diff --git a/.copywrite.hcl b/.copywrite.hcl
deleted file mode 100644
index 807c217..0000000
--- a/.copywrite.hcl
+++ /dev/null
@@ -1,15 +0,0 @@
-schema_version = 1
-
-project {
- license = "GPL-3.0-or-later"
- copyright_year = 2026
- copyright_holder = "Carl Philipp Klemm"
-
- # (OPTIONAL) A list of globs that should not have copyright/license headers.
- # Supports doublestar glob patterns for more flexibility in defining which
- # files or folders should be ignored
- header_ignore = [
- # "vendor/**",
- # "**autogen**",
- ]
-}
diff --git a/.github/actions/linux-setup-vulkan/action.yml b/.github/actions/linux-setup-vulkan/action.yml
deleted file mode 100644
index 4d29837..0000000
--- a/.github/actions/linux-setup-vulkan/action.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: "Linux - Setup Vulkan SDK"
-description: "Setup Vulkan SDK for Linux"
-inputs:
- path:
- description: "Installation path"
- required: true
- version:
- description: "Vulkan SDK version"
- required: true
-
-runs:
- using: "composite"
- steps:
- - name: Setup Vulkan SDK
- id: setup
- uses: ./.github/actions/unarchive-tar
- with:
- url: https://sdk.lunarg.com/sdk/download/${{ inputs.version }}/linux/vulkan_sdk.tar.xz
- path: ${{ inputs.path }}
- strip: 1
diff --git a/.github/actions/unarchive-tar/action.yml b/.github/actions/unarchive-tar/action.yml
deleted file mode 100644
index b97e402..0000000
--- a/.github/actions/unarchive-tar/action.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: "Unarchive tar"
-description: "Download and unarchive tar into directory"
-inputs:
- url:
- description: "URL of the tar archive"
- required: true
- path:
- description: "Directory to unarchive into"
- required: true
- type:
- description: "Compression type (tar option)"
- required: false
- default: "J"
- strip:
- description: "Strip components"
- required: false
- default: "0"
-
-runs:
- using: "composite"
- steps:
- - name: Unarchive into directory
- shell: bash
- run: |
- mkdir -p ${{ inputs.path }}
- cd ${{ inputs.path }}
- curl --no-progress-meter ${{ inputs.url }} | tar -${{ inputs.type }}x --strip-components=${{ inputs.strip }}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 66e114d..c9fb341 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,29 +6,17 @@ jobs:
Build:
runs-on: ubuntu-latest
steps:
- - name: Check out repository code
- uses: actions/checkout@v6
- with:
- submodules: recursive
- name: Install dependancies
- run: |
- sudo add-apt-repository -y ppa:kisak/kisak-mesa
- sudo apt update
- sudo apt install -y build-essential mesa-vulkan-drivers libusb-1.0-0-dev cmake qt6-base-dev qt6-base-dev-tools qt6-tools-dev-tools libqt6widgets6 libqt6multimedia6 qt6-multimedia-dev ffmpeg wget libglx-dev libgl1-mesa-dev qt6-qpa-plugins
- - name: Get latest Vulkan SDK version
- id: vulkan_sdk_version
- run: |
- echo "VULKAN_SDK_VERSION=$(curl https://vulkan.lunarg.com/sdk/latest/linux.txt)" >> "$GITHUB_ENV"
- - name: Setup Vulkan SDK
- uses: ./.github/actions/linux-setup-vulkan
- with:
- path: ./vulkan_sdk
- version: ${{ env.VULKAN_SDK_VERSION }}
+ run: sudo apt update; sudo apt install -y libusb-1.0-0-dev cmake qt6-base-dev qt6-base-dev-tools qt6-tools-dev-tools libqt6widgets6 libqt6multimedia6 wget libglx-dev libgl1-mesa-dev qt6-qpa-plugins
+ - name: Check out repository code
+ uses: ischanx/checkout@8c80eac3058d03dc5301629e8f7d59ae255d6cc3
+ - name: Checkout libeismultiplexer
+ run: git clone http://192.168.178.27/git/Eismultiplexer/libeismultiplexer.git
- name: Install Appimagetool
run: |
wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2)
chmod +x appimagetool-*-x86_64.AppImage
- sudo mv appimagetool-*-x86_64.AppImage /usr/bin/appimagetool
+ mv appimagetool-*-x86_64.AppImage /usr/bin/appimagetool
- name: Version
id: version
run: |
@@ -37,16 +25,13 @@ jobs:
echo "tag=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT
- name: Build
run: |
- source ./vulkan_sdk/setup-env.sh
mkdir ${{ github.workspace }}/build; cd ${{ github.workspace }}/build
- cmake -DCMAKE_BUILD_TYPE=Release -DGGML_VULKAN=ON -DCMAKE_INSTALL_PREFIX=./install/usr -DGIT_TAG=${{ steps.version.outputs.tag }} ..
+ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./install/usr -DGIT_TAG=${{ steps.version.outputs.tag }} ..
make -j $(nproc)
make install
- name: Create Appimage
run: |
cd ${{ github.workspace }}/build/
- install -d install/usr/lib/x86_64-linux-gnu/qt6/plugins/multimedia
- cp -vr /lib/x86_64-linux-gnu/qt6/plugins/multimedia/libffmpegmediaplugin.so install/usr/lib/x86_64-linux-gnu/qt6/plugins/multimedia/
appimagetool --appimage-extract-and-run -s deploy install/usr/share/applications/xyz.uvos.aceradio.desktop
appimagetool --appimage-extract-and-run -s install
- name: Upload Appimage
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index f58ab56..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "third_party/acestep.cpp"]
- path = third_party/acestep.cpp
- url = https://github.com/ServeurpersoCom/acestep.cpp.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1f0110..c3dcea3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,3 @@
-# Copyright Carl Philipp Klemm 2026
-# SPDX-License-Identifier: GPL-3.0-or-later
-
cmake_minimum_required(VERSION 3.14)
project(aceradio LANGUAGES CXX)
@@ -10,8 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find Qt packages
find_package(Qt6 COMPONENTS Core Gui Widgets Multimedia REQUIRED)
-# Add acestep.cpp subdirectory
-add_subdirectory(third_party/acestep.cpp)
+# Note: acestep.cpp binaries and models should be provided at runtime
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
@@ -41,29 +37,26 @@ add_executable(${PROJECT_NAME}
${MusicGeneratorGUI_H}
res/resources.qrc
src/elidedlabel.h src/elidedlabel.cpp
- src/SongItem.cpp
-
)
# UI file
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
-# Link libraries (Qt + acestep.cpp)
+# Link libraries (only Qt libraries - acestep.cpp is external)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Multimedia
- acestep-core
- ggml
)
-# Include directories
+# Include directories (only our source directory - acestep.cpp is external)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/third_party/acestep.cpp/src
)
+# Note: acestep.cpp binaries (ace-qwen3, dit-vae) and models should be provided at runtime
+
# Install targets
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
@@ -73,24 +66,3 @@ install(FILES res/xyz.uvos.aceradio.desktop DESTINATION share/applications)
# Install icon files
install(FILES res/xyz.uvos.aceradio.png DESTINATION share/icons/hicolor/256x256/apps RENAME xyz.uvos.aceradio.png)
install(FILES res/xyz.uvos.aceradio.svg DESTINATION share/icons/hicolor/scalable/apps RENAME xyz.uvos.aceradio.svg)
-
-# Test executable
-add_executable(test_acestep_worker
- tests/test_acestep_worker.cpp
- src/AceStepWorker.cpp
- src/AceStepWorker.h
- src/SongItem.cpp
- src/SongItem.h
-)
-
-target_link_libraries(test_acestep_worker PRIVATE
- Qt6::Core
- Qt6::Widgets
- acestep-core
- ggml
-)
-
-target_include_directories(test_acestep_worker PRIVATE
- ${CMAKE_CURRENT_SOURCE_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/third_party/acestep.cpp/src
-)
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index f288702..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
diff --git a/README.md b/README.md
index 1b7bfe9..2333021 100644
--- a/README.md
+++ b/README.md
@@ -3,65 +3,36 @@

A C++ Qt graphical user interface for generating music using [acestep.cpp](https://github.com/ServeurpersoCom/acestep.cpp).
-Provides endless playlist style music generation on ROCm, Vulkan or CUDA.
## Requirements
- Qt 6 Core, Gui, Widgets, and Multimedia
- CMake 3.14+
- acestep.cpp (bring your own binaries)
-- ROCm, cuda or vulkan headers and shader compiler
-
-## Installing
-
-Grab the latest release from https://github.com/IMbackK/aceradio/releases/
-
-## Models
-
-grab the models from [here](https://huggingface.co/Serveurperso/ACE-Step-1.5-GGUF/tree/main)
## Building
-### Linux / macOS
+### Build acestep.cpp first:
-1. clone the repo and create a build directory
- * `git clone https://github.com/IMbackK/aceradio.git && cd aceradio`
-2. create a build directory and enter it
- * `mkdir build && cd build`
-3. configure the project
- * `cmake -DGGML_HIP=on ..` for ROCm support.
- * `cmake -DGGML_CUDA=on ..` for CUDA support.
- * `cmake -DGGML_VULKAN=on ..` for Vulkan support.
-4. build the project
- `make -j$(nproc)`
+```bash
+git clone https://github.com/ServeurpersoCom/acestep.cpp.git
+cd acestep.cpp
+mkdir build && cd build
+cmake .. -DGGML_VULKAN=ON # or other backend
+make -j$(nproc)
+./models.sh # Download models (requires ~7.7 GB free space)
+```
-### Windows (Qt6 + CMake)
+### Build the GUI:
-To build on Windows run the following commands in *Command Prompt (cmd)* or *PowerShell*:
-
-1. Configure and Build:
-```cmd
+```bash
git clone https://github.com/IMbackK/aceradio.git
cd aceradio
-mkdir build
-cmake -B build -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2019_64"
-
-# Use --parallel to build using all CPU cores
-# Or use -j N (e.g., -j 4) to limit the number of threads
-cmake --build build --config Release --parallel
-```
-Note: Replace C:/Qt/6.x.x/msvc2019_64 with your actual Qt installation path.
-
-2. Deploy Dependencies:
-Run windeployqt to copy necessary Qt libraries to the build folder:
-```cmd
-"C:\Qt\6.x.x\msvc2019_64\bin\windeployqt.exe" --no-translations build\Release\aceradio.exe
+mkdir build && cd build
+cmake ..
+make -j$(nproc)
```
## Setup Paths:
-Go to settings->Ace Step->Model Paths and add the paths to the AceStep models.
-
-## License
-
-Aceradio and all its files, unless otherwise noted, are licensed to you under the GPL-3.0-or-later license
+Go to settings->Ace Step->Model Paths and add the paths to the acestep.cpp binaries the models.
diff --git a/res/xyz.uvos.aceradio.png b/res/xyz.uvos.aceradio.png
index 032045e..603d014 100644
Binary files a/res/xyz.uvos.aceradio.png and b/res/xyz.uvos.aceradio.png differ
diff --git a/res/xyz.uvos.aceradio.svg b/res/xyz.uvos.aceradio.svg
index 70a6ec5..23516fd 100644
--- a/res/xyz.uvos.aceradio.svg
+++ b/res/xyz.uvos.aceradio.svg
@@ -7,7 +7,7 @@
width="594"
height="594"
viewBox="0 0 594 594"
- sodipodi:docname="xyz.uvos.aceradio.svg"
+ sodipodi:docname="icon.svg"
inkscape:export-filename="icon64.png"
inkscape:export-xdpi="10.343434"
inkscape:export-ydpi="10.343434"
@@ -28,9 +28,9 @@
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="true"
- inkscape:zoom="0.82324218"
- inkscape:cx="38.8707"
- inkscape:cy="407.535"
+ inkscape:zoom="0.58212013"
+ inkscape:cx="-146.01797"
+ inkscape:cy="490.44859"
inkscape:window-width="1920"
inkscape:window-height="1054"
inkscape:window-x="0"
@@ -45,14 +45,6 @@
-
#include
#include
+#include
#include
+#include
#include
+#include
#include
-// acestep.cpp headers
-#include "pipeline-lm.h"
-#include "request.h"
-
-AceStepWorker::AceStepWorker(QObject* parent)
- : QObject(parent)
+AceStep::AceStep(QObject* parent): QObject(parent)
{
+ connect(&qwenProcess, &QProcess::finished, this, &AceStep::qwenProcFinished);
+ connect(&ditVaeProcess, &QProcess::finished, this, &AceStep::ditProcFinished);
}
-AceStepWorker::~AceStepWorker()
+bool AceStep::isGenerateing(SongItem* song)
{
- cancelGeneration();
- unloadModels();
+ if(!busy && song)
+ *song = this->request.song;
+ return busy;
}
-void AceStepWorker::setModelPaths(QString lmPath, QString textEncoderPath, QString ditPath, QString vaePath)
+void AceStep::cancleGenerateion()
{
- // Check if paths actually changed
- bool pathsChanged = (m_lmModelPath != lmPath || m_textEncoderPath != textEncoderPath ||
- m_ditPath != ditPath || m_vaePath != vaePath);
-
- m_lmModelPath = lmPath;
- m_textEncoderPath = textEncoderPath;
- m_ditPath = ditPath;
- m_vaePath = vaePath;
-
- // Cache as byte arrays to avoid dangling pointers
- m_lmModelPathBytes = lmPath.toUtf8();
- m_textEncoderPathBytes = textEncoderPath.toUtf8();
- m_ditPathBytes = ditPath.toUtf8();
- m_vaePathBytes = vaePath.toUtf8();
-
- // If paths changed and models are loaded, unload them so they'll be reloaded with new paths
- if (pathsChanged && m_modelsLoaded.load())
+ qwenProcess.blockSignals(true);
+ qwenProcess.terminate();
+ qwenProcess.waitForFinished();
+ qwenProcess.blockSignals(false);
+
+ ditVaeProcess.blockSignals(true);
+ ditVaeProcess.terminate();
+ ditVaeProcess.waitForFinished();
+ ditVaeProcess.blockSignals(false);
+
+ progressUpdate(100);
+ if(busy)
+ generationCancled(request.song);
+
+ busy = false;
+}
+
+bool AceStep::requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath,
+ QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath,
+ QString vaeModelPath)
+{
+ if(busy)
{
- unloadModels();
+ qWarning()<<"Droping song:"<generate(), aceStepPath, textEncoderModelPath, ditModelPath, vaeModelPath};
-void AceStepWorker::setFlashAttention(bool enabled)
-{
- m_flashAttention = enabled;
-}
-
-bool AceStepWorker::isGenerating(SongItem* song)
-{
- if (!m_busy.load() && song)
- *song = m_currentSong;
- return m_busy.load();
-}
-
-void AceStepWorker::cancelGeneration()
-{
- m_cancelRequested.store(true);
-}
-
-bool AceStepWorker::requestGeneration(SongItem song, QString requestTemplate)
-{
- if (m_busy.load())
+ QString qwen3Binary = aceStepPath + "/ace-qwen3";
+ QFileInfo qwen3Info(qwen3Binary);
+ if (!qwen3Info.exists() || !qwen3Info.isExecutable())
{
- qWarning() << "Dropping song:" << song.caption;
+ generationError("ace-qwen3 binary not found at: " + qwen3Binary);
+ busy = false;
+ return false;
+ }
+ if (!QFileInfo::exists(qwen3ModelPath))
+ {
+ generationError("Qwen3 model not found: " + qwen3ModelPath);
+ busy = false;
+ return false;
+ }
+ if (!QFileInfo::exists(textEncoderModelPath))
+ {
+ generationError("Text encoder model not found: " + textEncoderModelPath);
+ busy = false;
+ return false;
+ }
+ if (!QFileInfo::exists(ditModelPath))
+ {
+ generationError("DiT model not found: " + ditModelPath);
+ busy = false;
+ return false;
+ }
+ if (!QFileInfo::exists(vaeModelPath))
+ {
+ generationError("VAE model not found: " + vaeModelPath);
+ busy = false;
return false;
}
- m_busy.store(true);
- m_cancelRequested.store(false);
- m_currentSong = song;
- m_requestTemplate = requestTemplate;
- m_uid = QRandomGenerator::global()->generate();
+ request.requestFilePath = tempDir + "/request_" + QString::number(request.uid) + ".json";
- // Validate model paths
- if (m_lmModelPath.isEmpty() || m_textEncoderPath.isEmpty() ||
- m_ditPath.isEmpty() || m_vaePath.isEmpty())
+ QJsonParseError parseError;
+ QJsonDocument templateDoc = QJsonDocument::fromJson(requestTemplate.toUtf8(), &parseError);
+ if (!templateDoc.isObject())
{
- emit generationError("Model paths not set. Call setModelPaths() first.");
- m_busy.store(false);
+ generationError("Invalid JSON template: " + QString(parseError.errorString()));
+ busy = false;
return false;
}
- // Validate model files exist
- if (!QFileInfo::exists(m_lmModelPath))
- {
- emit generationError("LM model not found: " + m_lmModelPath);
- m_busy.store(false);
- return false;
- }
- if (!QFileInfo::exists(m_textEncoderPath))
- {
- emit generationError("Text encoder model not found: " + m_textEncoderPath);
- m_busy.store(false);
- return false;
- }
- if (!QFileInfo::exists(m_ditPath))
- {
- emit generationError("DiT model not found: " + m_ditPath);
- m_busy.store(false);
- return false;
- }
- if (!QFileInfo::exists(m_vaePath))
- {
- emit generationError("VAE model not found: " + m_vaePath);
- m_busy.store(false);
- return false;
- }
+ QJsonObject requestObj = templateDoc.object();
+ requestObj["caption"] = song.caption;
- // Run generation in the worker thread
- QMetaObject::invokeMethod(this, "runGeneration", Qt::QueuedConnection);
+ if (!song.lyrics.isEmpty())
+ requestObj["lyrics"] = song.lyrics;
+ if (!song.vocalLanguage.isEmpty())
+ requestObj["vocal_language"] = song.vocalLanguage;
+
+ // Write the request file
+ QFile requestFileHandle(request.requestFilePath);
+ if (!requestFileHandle.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ emit generationError("Failed to create request file: " + requestFileHandle.errorString());
+ busy = false;
+ return false;
+ }
+ requestFileHandle.write(QJsonDocument(requestObj).toJson(QJsonDocument::Indented));
+ requestFileHandle.close();
+
+ QStringList qwen3Args;
+ qwen3Args << "--request" << request.requestFilePath;
+ qwen3Args << "--model" << qwen3ModelPath;
+
+ progressUpdate(30);
+
+ qwenProcess.start(qwen3Binary, qwen3Args);
return true;
}
-bool AceStepWorker::checkCancel(void* data)
+void AceStep::qwenProcFinished(int code, QProcess::ExitStatus status)
{
- AceStepWorker* worker = static_cast(data);
- return worker->m_cancelRequested.load();
-}
-
-std::shared_ptr AceStepWorker::convertToWav(const AceAudio& audio)
-{
- auto audioData = std::make_shared();
-
- // Simple WAV header + stereo float data
- int numChannels = 2;
- int bitsPerSample = 16;
- int byteRate = audio.sample_rate * numChannels * (bitsPerSample / 8);
- int blockAlign = numChannels * (bitsPerSample / 8);
- int dataSize = audio.n_samples * numChannels * (bitsPerSample / 8);
-
- // RIFF header
- audioData->append("RIFF");
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&dataSize), 4));
- audioData->append("WAVE");
-
- // fmt chunk
- audioData->append("fmt ");
- int fmtSize = 16;
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&fmtSize), 4));
- short audioFormat = 1; // PCM
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&audioFormat), 2));
- short numCh = numChannels;
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&numCh), 2));
- int sampleRate = audio.sample_rate;
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&sampleRate), 4));
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&byteRate), 4));
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&blockAlign), 2));
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&bitsPerSample), 2));
-
- // data chunk
- audioData->append("data");
- audioData->append(QByteArray::fromRawData(reinterpret_cast(&dataSize), 4));
-
- // Convert float samples to 16-bit and write
- QVector interleaved(audio.n_samples * numChannels);
- for (int i = 0; i < audio.n_samples; i++)
+ QFile::remove(request.requestFilePath);
+ if(code != 0)
{
- float left = audio.samples[i];
- float right = audio.samples[i + audio.n_samples];
- // Clamp and convert to 16-bit
- left = std::max(-1.0f, std::min(1.0f, left));
- right = std::max(-1.0f, std::min(1.0f, right));
- interleaved[i * 2] = static_cast(left * 32767.0f);
- interleaved[i * 2 + 1] = static_cast(right * 32767.0f);
- }
- audioData->append(QByteArray::fromRawData(reinterpret_cast(interleaved.data()), dataSize));
- return audioData;
-}
-
-void AceStepWorker::runGeneration()
-{
- // Convert SongItem to AceRequest
- AceRequest req = songToRequest(m_currentSong, m_requestTemplate);
- AceRequest lmOutput;
- request_init(&lmOutput);
-
- emit progressUpdate(10);
-
- if (!loadLm())
- {
- m_busy.store(false);
+ QString errorOutput = qwenProcess.readAllStandardError();
+ generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput);
+ busy = false;
return;
}
- emit progressUpdate(30);
-
- int lmResult = ace_lm_generate(m_lmContext, &req, 1, &lmOutput,
- nullptr, nullptr,
- checkCancel, this,
- LM_MODE_GENERATE);
-
- if (m_cancelRequested.load())
+ QString ditVaeBinary = request.aceStepPath + "/dit-vae";
+ QFileInfo ditVaeInfo(ditVaeBinary);
+ if (!ditVaeInfo.exists() || !ditVaeInfo.isExecutable())
{
- if(m_lowVramMode)
- unloadModels();
- emit generationCanceled(m_currentSong);
- m_busy.store(false);
+ generationError("dit-vae binary not found at: " + ditVaeBinary);
+ busy = false;
return;
}
- if (lmResult != 0)
+ request.requestLlmFilePath = tempDir + "/request_" + QString::number(request.uid) + "0.json";
+ if (!QFileInfo::exists(request.requestLlmFilePath))
{
- if(m_lowVramMode)
- unloadModels();
- emit generationError("LM generation failed or was canceled");
- m_busy.store(false);
+ generationError("ace-qwen3 failed to create enhaced request file "+request.requestLlmFilePath);
+ busy = false;
return;
}
- m_currentSong.lyrics = QString::fromStdString(lmOutput.lyrics);
-
- if(m_lowVramMode)
- unloadLm();
-
- emit progressUpdate(50);
-
- if (!loadSynth())
- {
- m_busy.store(false);
- return;
- }
-
- emit progressUpdate(60);
-
- AceAudio outputAudio;
- outputAudio.samples = nullptr;
- outputAudio.n_samples = 0;
- outputAudio.sample_rate = 48000;
-
- int synthResult = ace_synth_generate(m_synthContext, &lmOutput,
- nullptr, 0, // no source audio
- nullptr, 0, // no reference audio
- 1, &outputAudio,
- checkCancel, this);
-
- if(m_lowVramMode)
- unloadSynth();
-
- if (m_cancelRequested.load())
- {
- emit generationCanceled(m_currentSong);
- m_busy.store(false);
- return;
- }
-
- if (synthResult != 0)
- {
- emit generationError("Synthesis failed or was canceled");
- m_busy.store(false);
- return;
- }
-
- std::shared_ptr audioData = convertToWav(outputAudio);
- ace_audio_free(&outputAudio);
-
- m_currentSong.json = QString::fromStdString(request_to_json(&lmOutput, true));
- m_currentSong.audioData = audioData;
-
- if (lmOutput.bpm > 0)
- m_currentSong.bpm = lmOutput.bpm;
-
- if (!lmOutput.keyscale.empty())
- m_currentSong.key = QString::fromStdString(lmOutput.keyscale);
-
- emit progressUpdate(100);
- emit songGenerated(m_currentSong);
-
- m_busy.store(false);
-}
-
-bool AceStepWorker::loadModels()
-{
- bool ret = loadSynth();
- if(!ret)
- return false;
-
- ret = loadLm();
- if(!ret)
- return false;
- return true;
-}
-
-void AceStepWorker::unloadModels()
-{
- if (m_synthContext)
- {
- ace_synth_free(m_synthContext);
- m_synthContext = nullptr;
- }
- if (m_lmContext)
- {
- ace_lm_free(m_lmContext);
- m_lmContext = nullptr;
- }
- m_modelsLoaded.store(false);
-}
-
-bool AceStepWorker::loadLm()
-{
- if (m_lmContext)
- return true;
-
- AceLmParams lmParams;
- ace_lm_default_params(&lmParams);
- lmParams.model_path = m_lmModelPathBytes.constData();
- lmParams.use_fsm = true;
- lmParams.use_fa = m_flashAttention;
-
- m_lmContext = ace_lm_load(&lmParams);
- if (!m_lmContext)
- {
- emit generationError("Failed to load LM model: " + m_lmModelPath);
- return false;
- }
- return true;
-}
-
-void AceStepWorker::unloadLm()
-{
- if (m_lmContext)
- {
- ace_lm_free(m_lmContext);
- m_lmContext = nullptr;
- }
-}
-
-bool AceStepWorker::loadSynth()
-{
- if (m_synthContext)
- return true;
-
- AceSynthParams synthParams;
- ace_synth_default_params(&synthParams);
- synthParams.text_encoder_path = m_textEncoderPathBytes.constData();
- synthParams.dit_path = m_ditPathBytes.constData();
- synthParams.vae_path = m_vaePathBytes.constData();
- synthParams.use_fa = m_flashAttention;
-
- m_synthContext = ace_synth_load(&synthParams);
- if (!m_synthContext)
- {
- emit generationError("Failed to load synthesis models");
- return false;
- }
- return true;
-}
-
-void AceStepWorker::unloadSynth()
-{
- if (m_synthContext)
- {
- ace_synth_free(m_synthContext);
- m_synthContext = nullptr;
- }
-}
-
-AceRequest AceStepWorker::songToRequest(const SongItem& song, const QString& templateJson)
-{
- AceRequest req;
- request_init(&req);
-
- // Parse template first to get all default values
- QJsonObject requestJson;
- if (!templateJson.isEmpty())
+ // Load lyrics from the enhanced request file
+ QFile lmOutputFile(request.requestLlmFilePath);
+ if (lmOutputFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QJsonParseError parseError;
- QJsonDocument templateDoc = QJsonDocument::fromJson(templateJson.toUtf8(), &parseError);
- if (templateDoc.isObject())
- requestJson = templateDoc.object();
- else
- qWarning() << "Failed to parse request template:" << parseError.errorString();
+ request.song.json = lmOutputFile.readAll();
+ QJsonDocument doc = QJsonDocument::fromJson(request.song.json.toUtf8(), &parseError);
+ lmOutputFile.close();
+
+ if (doc.isObject() && !parseError.error)
+ {
+ QJsonObject obj = doc.object();
+ if (obj.contains("lyrics") && obj["lyrics"].isString())
+ request.song.lyrics = obj["lyrics"].toString();
+ }
}
- song.store(requestJson);
- QJsonDocument requestJsonDoc(requestJson);
- QString requestJsonString = QString::fromUtf8(requestJsonDoc.toJson(QJsonDocument::Compact));
- if (!request_parse_json(&req, requestJsonString.toUtf8()))
- qWarning() << "Failed to parse merged request JSON";
+ // Step 2: Run dit-vae to generate audio
+ QStringList ditVaeArgs;
+ ditVaeArgs << "--request"<(QRandomGenerator::global()->generate());
+ progressUpdate(60);
+
+ ditVaeProcess.start(ditVaeBinary, ditVaeArgs);
+}
+
+void AceStep::ditProcFinished(int code, QProcess::ExitStatus status)
+{
+ QFile::remove(request.requestLlmFilePath);
+ if (code != 0)
+ {
+ QString errorOutput = ditVaeProcess.readAllStandardError();
+ generationError("dit-vae exited with code " + QString::number(code) + ": " + errorOutput);
+ busy = false;
+ return;
+ }
+
+ // Find the generated WAV file
+ QString wavFile = tempDir+"/request_" + QString::number(request.uid) + "00.wav";
+ if (!QFileInfo::exists(wavFile))
+ {
+ generationError("No WAV file generated at "+wavFile);
+ busy = false;
+ return;
+ }
+ busy = false;
+
+ progressUpdate(100);
+ request.song.file = wavFile;
+ songGenerated(request.song);
+}
- return req;
-}
\ No newline at end of file
diff --git a/src/AceStepWorker.h b/src/AceStepWorker.h
index 56d95b1..41c4676 100644
--- a/src/AceStepWorker.h
+++ b/src/AceStepWorker.h
@@ -1,98 +1,56 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef ACESTEPWORKER_H
#define ACESTEPWORKER_H
#include
#include
-#include
+#include
#include
-#include
#include "SongItem.h"
-#include "pipeline-synth.h"
-#include "request.h"
-struct AceLm;
-struct AceSynth;
-
-class AceStepWorker : public QObject
+class AceStep : public QObject
{
Q_OBJECT
+ QProcess qwenProcess;
+ QProcess ditVaeProcess;
-public:
- explicit AceStepWorker(QObject* parent = nullptr);
- ~AceStepWorker();
+ bool busy = false;
- bool isGenerating(SongItem* song = nullptr);
- void cancelGeneration();
+ struct Request
+ {
+ SongItem song;
+ uint64_t uid;
+ QString aceStepPath;
+ QString textEncoderModelPath;
+ QString ditModelPath;
+ QString vaeModelPath;
+ QString requestFilePath;
+ QString requestLlmFilePath;
+ };
- // Model paths - set these before first generation
- void setModelPaths(QString lmPath, QString textEncoderPath, QString ditPath, QString vaePath);
+ Request request;
- // Low VRAM mode: unload models between phases to save VRAM
- void setLowVramMode(bool enabled);
- bool isLowVramMode() const { return m_lowVramMode; }
-
- // Flash attention mode
- void setFlashAttention(bool enabled);
- bool isFlashAttention() const { return m_flashAttention; }
-
- // Request a new song generation
- bool requestGeneration(SongItem song, QString requestTemplate);
+ const QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
signals:
void songGenerated(SongItem song);
- void generationCanceled(SongItem song);
+ void generationCancled(SongItem song);
void generationError(QString error);
void progressUpdate(int progress);
+public slots:
+ bool requestGeneration(SongItem song, QString requestTemplate, QString aceStepPath,
+ QString qwen3ModelPath, QString textEncoderModelPath, QString ditModelPath,
+ QString vaeModelPath);
+
+public:
+ AceStep(QObject* parent = nullptr);
+ bool isGenerateing(SongItem* song = nullptr);
+ void cancleGenerateion();
+
private slots:
- void runGeneration();
-
-private:
- static bool checkCancel(void* data);
-
- bool loadModels();
- void unloadModels();
- bool loadLm();
- void unloadLm();
- bool loadSynth();
- void unloadSynth();
-
- AceRequest songToRequest(const SongItem& song, const QString& templateJson);
-
- static std::shared_ptr convertToWav(const AceAudio& audio);
-
- // Generation state
- std::atomic m_busy{false};
- std::atomic m_cancelRequested{false};
- std::atomic m_modelsLoaded{false};
- bool m_lowVramMode = false;
- bool m_flashAttention = true;
-
- // Current request data
- SongItem m_currentSong;
- QString m_requestTemplate;
- uint64_t m_uid;
-
- // Model paths
- QString m_lmModelPath;
- QString m_textEncoderPath;
- QString m_ditPath;
- QString m_vaePath;
-
- // Loaded models (accessed from worker thread only)
- AceLm* m_lmContext = nullptr;
- AceSynth* m_synthContext = nullptr;
-
- QByteArray m_lmModelPathBytes;
- QByteArray m_textEncoderPathBytes;
- QByteArray m_ditPathBytes;
- QByteArray m_vaePathBytes;
+ void qwenProcFinished(int code, QProcess::ExitStatus status);
+ void ditProcFinished(int code, QProcess::ExitStatus status);
};
-#endif // ACESTEPWORKER_H
\ No newline at end of file
+#endif // ACESTEPWORKER_H
diff --git a/src/AdvancedSettingsDialog.cpp b/src/AdvancedSettingsDialog.cpp
index 7e2e412..163ba8a 100644
--- a/src/AdvancedSettingsDialog.cpp
+++ b/src/AdvancedSettingsDialog.cpp
@@ -1,6 +1,3 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "AdvancedSettingsDialog.h"
#include "ui_AdvancedSettingsDialog.h"
#include
@@ -13,12 +10,6 @@ AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent)
ui(new Ui::AdvancedSettingsDialog)
{
ui->setupUi(this);
-
- // Connect signals and slots explicitly
- connect(ui->qwen3BrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onQwen3BrowseButtonClicked);
- connect(ui->textEncoderBrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onTextEncoderBrowseButtonClicked);
- connect(ui->ditBrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onDiTBrowseButtonClicked);
- connect(ui->vaeBrowseButton, &QPushButton::clicked, this, &AdvancedSettingsDialog::onVAEBrowseButtonClicked);
}
AdvancedSettingsDialog::~AdvancedSettingsDialog()
@@ -31,6 +22,11 @@ QString AdvancedSettingsDialog::getJsonTemplate() const
return ui->jsonTemplateEdit->toPlainText();
}
+QString AdvancedSettingsDialog::getAceStepPath() const
+{
+ return ui->aceStepPathEdit->text();
+}
+
QString AdvancedSettingsDialog::getQwen3ModelPath() const
{
return ui->qwen3ModelEdit->text();
@@ -51,21 +47,16 @@ QString AdvancedSettingsDialog::getVAEModelPath() const
return ui->vaeModelEdit->text();
}
-bool AdvancedSettingsDialog::getLowVramMode() const
-{
- return ui->lowVramCheckBox->isChecked();
-}
-
-bool AdvancedSettingsDialog::getFlashAttention() const
-{
- return ui->flashAttentionCheckBox->isChecked();
-}
-
void AdvancedSettingsDialog::setJsonTemplate(const QString &templateStr)
{
ui->jsonTemplateEdit->setPlainText(templateStr);
}
+void AdvancedSettingsDialog::setAceStepPath(const QString &path)
+{
+ ui->aceStepPathEdit->setText(path);
+}
+
void AdvancedSettingsDialog::setQwen3ModelPath(const QString &path)
{
ui->qwen3ModelEdit->setText(path);
@@ -86,17 +77,16 @@ void AdvancedSettingsDialog::setVAEModelPath(const QString &path)
ui->vaeModelEdit->setText(path);
}
-void AdvancedSettingsDialog::setLowVramMode(bool enabled)
+void AdvancedSettingsDialog::on_aceStepBrowseButton_clicked()
{
- ui->lowVramCheckBox->setChecked(enabled);
+ QString dir = QFileDialog::getExistingDirectory(this, "Select AceStep Build Directory", ui->aceStepPathEdit->text());
+ if (!dir.isEmpty())
+ {
+ ui->aceStepPathEdit->setText(dir);
+ }
}
-void AdvancedSettingsDialog::setFlashAttention(bool enabled)
-{
- ui->flashAttentionCheckBox->setChecked(enabled);
-}
-
-void AdvancedSettingsDialog::onQwen3BrowseButtonClicked()
+void AdvancedSettingsDialog::on_qwen3BrowseButton_clicked()
{
QString file = QFileDialog::getOpenFileName(this, "Select Qwen3 Model", ui->qwen3ModelEdit->text(),
"GGUF Files (*.gguf)");
@@ -106,7 +96,7 @@ void AdvancedSettingsDialog::onQwen3BrowseButtonClicked()
}
}
-void AdvancedSettingsDialog::onTextEncoderBrowseButtonClicked()
+void AdvancedSettingsDialog::on_textEncoderBrowseButton_clicked()
{
QString file = QFileDialog::getOpenFileName(this, "Select Text Encoder Model", ui->textEncoderEdit->text(),
"GGUF Files (*.gguf)");
@@ -116,7 +106,7 @@ void AdvancedSettingsDialog::onTextEncoderBrowseButtonClicked()
}
}
-void AdvancedSettingsDialog::onDiTBrowseButtonClicked()
+void AdvancedSettingsDialog::on_ditBrowseButton_clicked()
{
QString file = QFileDialog::getOpenFileName(this, "Select DiT Model", ui->ditModelEdit->text(), "GGUF Files (*.gguf)");
if (!file.isEmpty())
@@ -125,7 +115,7 @@ void AdvancedSettingsDialog::onDiTBrowseButtonClicked()
}
}
-void AdvancedSettingsDialog::onVAEBrowseButtonClicked()
+void AdvancedSettingsDialog::on_vaeBrowseButton_clicked()
{
QString file = QFileDialog::getOpenFileName(this, "Select VAE Model", ui->vaeModelEdit->text(), "GGUF Files (*.gguf)");
if (!file.isEmpty())
diff --git a/src/AdvancedSettingsDialog.h b/src/AdvancedSettingsDialog.h
index 8a3047a..fb4aa69 100644
--- a/src/AdvancedSettingsDialog.h
+++ b/src/AdvancedSettingsDialog.h
@@ -1,8 +1,3 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef ADVANCEDSETTINGSDIALOG_H
#define ADVANCEDSETTINGSDIALOG_H
@@ -24,27 +19,26 @@ public:
// Getters for settings
QString getJsonTemplate() const;
+ QString getAceStepPath() const;
QString getQwen3ModelPath() const;
QString getTextEncoderModelPath() const;
QString getDiTModelPath() const;
QString getVAEModelPath() const;
- bool getLowVramMode() const;
- bool getFlashAttention() const;
// Setters for settings
void setJsonTemplate(const QString &templateStr);
+ void setAceStepPath(const QString &path);
void setQwen3ModelPath(const QString &path);
void setTextEncoderModelPath(const QString &path);
void setDiTModelPath(const QString &path);
void setVAEModelPath(const QString &path);
- void setLowVramMode(bool enabled);
- void setFlashAttention(bool enabled);
private slots:
- void onQwen3BrowseButtonClicked();
- void onTextEncoderBrowseButtonClicked();
- void onDiTBrowseButtonClicked();
- void onVAEBrowseButtonClicked();
+ void on_aceStepBrowseButton_clicked();
+ void on_qwen3BrowseButton_clicked();
+ void on_textEncoderBrowseButton_clicked();
+ void on_ditBrowseButton_clicked();
+ void on_vaeBrowseButton_clicked();
private:
Ui::AdvancedSettingsDialog *ui;
diff --git a/src/AdvancedSettingsDialog.ui b/src/AdvancedSettingsDialog.ui
index 6e95e90..e9fadf1 100644
--- a/src/AdvancedSettingsDialog.ui
+++ b/src/AdvancedSettingsDialog.ui
@@ -1,239 +1,203 @@
-
- AdvancedSettingsDialog
-
-
-
- 0
- 0
- 600
- 450
-
-
-
- Advanced Settings
-
-
- -
-
-
- 0
-
-
-
- Performance
-
-
-
-
-
-
- Low VRAM Mode
-
-
-
- -
-
-
- Unload models between generation phases to save VRAM. Slower but uses less memory.
-
-
- true
-
-
-
- -
-
-
- Flash Attention
-
-
- true
-
-
-
- -
-
-
- Use flash attention for faster generation. Disable if experiencing poor output quality on vulkan.
-
-
- true
-
-
-
- -
-
-
- Qt::Orientation::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
- JSON Template
-
-
- -
-
-
- JSON Template for AceStep generation:
-
-
- true
-
-
-
- -
-
-
-
-
-
-
- Model Paths
-
-
-
- QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow
-
- -
-
-
- Qwen3 Model:
-
-
-
- -
-
-
-
-
-
- -
-
-
- Browse...
-
-
-
-
-
- -
-
-
- Text Encoder Model:
-
-
-
- -
-
-
-
-
-
- -
-
-
- Browse...
-
-
-
-
-
- -
-
-
- DiT Model:
-
-
-
- -
-
-
-
-
-
- -
-
-
- Browse...
-
-
-
-
-
- -
-
-
- VAE Model:
-
-
-
- -
-
-
-
-
-
- -
-
-
- Browse...
-
-
-
-
-
-
-
-
-
- -
-
-
- QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save
-
-
-
-
-
-
-
-
- buttonBox
- accepted()
- AdvancedSettingsDialog
- accept()
-
-
- 248
- 254
-
-
- 157
- 254
-
-
-
-
- buttonBox
- rejected()
- AdvancedSettingsDialog
- reject()
-
-
- 316
- 260
-
-
- 286
- 260
-
-
-
-
-
+
+ AdvancedSettingsDialog
+
+
+
+ 0
+ 0
+ 600
+ 450
+
+
+
+ Advanced Settings
+
+
+ -
+
+
+ 0
+
+
+
+ JSON Template
+
+
+
-
+
+
+ JSON Template for AceStep generation:
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+
+ Model Paths
+
+
+
+ QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow
+
+ -
+
+
+ AceStep Path:
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ Qwen3 Model:
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ Text Encoder Model:
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ DiT Model:
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ VAE Model:
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ AdvancedSettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 254
+
+
+
+
+ buttonBox
+ rejected()
+ AdvancedSettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 260
+
+
+
+
+
diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp
index 8374027..76694d4 100644
--- a/src/AudioPlayer.cpp
+++ b/src/AudioPlayer.cpp
@@ -1,8 +1,4 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "AudioPlayer.h"
-#include
#include
AudioPlayer::AudioPlayer(QObject *parent)
@@ -49,33 +45,6 @@ void AudioPlayer::play(const QString &filePath)
positionTimer->start();
}
-void AudioPlayer::play(std::shared_ptr audioData)
-{
- if (isPlaying())
- {
- stop();
- }
-
- if (!audioData || audioData->isEmpty())
- {
- emit playbackError("No audio data available");
- return;
- }
-
- // Create a buffer with the audio data
- QBuffer *buffer = new QBuffer();
- buffer->setData(*audioData);
- buffer->open(QIODevice::ReadOnly);
- buffer->setParent(this);
-
- // Use QMediaPlayer::setSourceDevice for in-memory playback
- mediaPlayer->setSourceDevice(buffer);
- mediaPlayer->play();
-
- // Start position timer
- positionTimer->start();
-}
-
void AudioPlayer::play()
{
if (!isPlaying())
@@ -120,19 +89,6 @@ int AudioPlayer::position() const
return mediaPlayer->position();
}
-void AudioPlayer::setVolume(int volume)
-{
- // Convert from 0-100 range to 0.0-1.0 range
- qreal volumeLevel = static_cast(volume) / 100.0;
- audioOutput->setVolume(volumeLevel);
-}
-
-int AudioPlayer::getVolume() const
-{
- // Convert from 0.0-1.0 range to 0-100 range
- return static_cast(audioOutput->volume() * 100);
-}
-
void AudioPlayer::handlePlaybackStateChanged(QMediaPlayer::PlaybackState state)
{
if (state == QMediaPlayer::PlayingState)
diff --git a/src/AudioPlayer.h b/src/AudioPlayer.h
index 144010f..93f14b2 100644
--- a/src/AudioPlayer.h
+++ b/src/AudioPlayer.h
@@ -1,8 +1,3 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef AUDIOPLAYER_H
#define AUDIOPLAYER_H
@@ -14,7 +9,6 @@
#include
#include
#include
-#include
class AudioPlayer : public QObject
{
@@ -24,7 +18,6 @@ public:
~AudioPlayer();
void play(const QString &filePath);
- void play(std::shared_ptr audioData);
void play();
void stop();
void pause();
@@ -32,8 +25,6 @@ public:
bool isPlaying() const;
int duration() const;
int position() const;
- void setVolume(int volume);
- int getVolume() const;
signals:
void playbackStarted();
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 565e8a4..62c8a63 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,6 +1,3 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "SongDialog.h"
@@ -21,7 +18,7 @@ MainWindow::MainWindow(QWidget *parent)
ui(new Ui::MainWindow),
songModel(new SongListModel(this)),
audioPlayer(new AudioPlayer(this)),
- aceStep(new AceStepWorker),
+ aceStep(new AceStep(this)),
playbackTimer(new QTimer(this)),
isPlaying(false),
isPaused(false),
@@ -29,7 +26,6 @@ MainWindow::MainWindow(QWidget *parent)
isGeneratingNext(false)
{
aceStep->moveToThread(&aceThread);
- aceThread.setObjectName("AceStep Woker Thread");
ui->setupUi(this);
@@ -42,27 +38,15 @@ MainWindow::MainWindow(QWidget *parent)
// Load settings
loadSettings();
- // Set model paths for acestep.cpp
- aceStep->setModelPaths(qwen3ModelPath, textEncoderModelPath, ditModelPath, vaeModelPath);
-
// Auto-load playlist from config location on startup
autoLoadPlaylist();
// Connect signals and slots
- connect(ui->playButton, &QPushButton::clicked, this, &MainWindow::onPlayButtonClicked);
- connect(ui->pauseButton, &QPushButton::clicked, this, &MainWindow::onPauseButtonClicked);
- connect(ui->skipButton, &QPushButton::clicked, this, &MainWindow::onSkipButtonClicked);
- connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::onStopButtonClicked);
- connect(ui->shuffleButton, &QPushButton::clicked, this, &MainWindow::onShuffleButtonClicked);
- connect(ui->volumeSlider, &QSlider::valueChanged, this, &MainWindow::onVolumeSliderValueChanged);
- connect(ui->addSongButton, &QPushButton::clicked, this, &MainWindow::onAddSongButtonClicked);
- connect(ui->removeSongButton, &QPushButton::clicked, this, &MainWindow::onRemoveSongButtonClicked);
- connect(ui->actionAdvancedSettings, &QAction::triggered, this, &MainWindow::onAdvancedSettingsButtonClicked);
- connect(ui->actionSavePlaylist, &QAction::triggered, this, &MainWindow::onActionSavePlaylist);
- connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::onActionLoadPlaylist);
- connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::onActionAppendPlaylist);
- connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::onActionSaveSong);
- connect(ui->positionSlider, &QSlider::sliderMoved, this, &MainWindow::onPositionSliderSliderMoved);
+ connect(ui->actionAdvancedSettings, &QAction::triggered, this, &MainWindow::on_advancedSettingsButton_clicked);
+ connect(ui->actionSavePlaylist, &QAction::triggered, this, &MainWindow::on_actionSavePlaylist);
+ connect(ui->actionLoadPlaylist, &QAction::triggered, this, &MainWindow::on_actionLoadPlaylist);
+ connect(ui->actionAppendPlaylist, &QAction::triggered, this, &MainWindow::on_actionAppendPlaylist);
+ connect(ui->actionSaveSong, &QAction::triggered, this, &MainWindow::on_actionSaveSong);
connect(ui->actionQuit, &QAction::triggered, this, [this]()
{
close();
@@ -75,16 +59,16 @@ MainWindow::MainWindow(QWidget *parent)
connect(audioPlayer, &AudioPlayer::playbackStarted, this, &MainWindow::playbackStarted);
connect(audioPlayer, &AudioPlayer::positionChanged, this, &MainWindow::updatePosition);
connect(audioPlayer, &AudioPlayer::durationChanged, this, &MainWindow::updateDuration);
- connect(aceStep, &AceStepWorker::songGenerated, this, &MainWindow::songGenerated);
- connect(aceStep, &AceStepWorker::generationCanceled, this, &MainWindow::generationCanceld);
- connect(aceStep, &AceStepWorker::generationError, this, &MainWindow::generationError);
- connect(aceStep, &AceStepWorker::progressUpdate, ui->progressBar, &QProgressBar::setValue);
+ connect(aceStep, &AceStep::songGenerated, this, &MainWindow::songGenerated);
+ connect(aceStep, &AceStep::generationCancled, this, &MainWindow::generationCanceld);
+ connect(aceStep, &AceStep::generationError, this, &MainWindow::generationError);
+ connect(aceStep, &AceStep::progressUpdate, ui->progressBar, &QProgressBar::setValue);
// Connect double-click on song list for editing (works with QTableView too)
- connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::onSongListViewDoubleClicked);
+ connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
// Connect audio player error signal
- connect(audioPlayer, &AudioPlayer::playbackError, this, [this](const QString &error)
+ connect(audioPlayer, &AudioPlayer::playbackError, [this](const QString &error)
{
QMessageBox::warning(this, "Playback Error", "Failed to play audio: " + error);
});
@@ -111,20 +95,11 @@ MainWindow::MainWindow(QWidget *parent)
ui->nowPlayingLabel->setText("Now Playing:");
currentSong = songModel->getSong(0);
-
- // Set default volume (50% from UI)
- audioPlayer->setVolume(ui->volumeSlider->value());
-
- // Start the worker thread and enter its event loop
- QObject::connect(&aceThread, &QThread::started, [this]() {qDebug() << "Worker thread started";});
- aceThread.start();
}
MainWindow::~MainWindow()
{
- aceStep->cancelGeneration();
- aceThread.quit();
- aceThread.wait();
+ aceStep->cancleGenerateion();
autoSavePlaylist();
saveSettings();
@@ -165,26 +140,15 @@ void MainWindow::loadSettings()
shuffleMode = settings.value("shuffleMode", false).toBool();
ui->shuffleButton->setChecked(shuffleMode);
- // Load volume setting
- int savedVolume = settings.value("volume", 50).toInt();
- ui->volumeSlider->setValue(savedVolume);
-
// Load path settings with defaults based on application directory
QString appDir = QCoreApplication::applicationDirPath();
+ aceStepPath = settings.value("aceStepPath", appDir + "/acestep.cpp").toString();
qwen3ModelPath = settings.value("qwen3ModelPath",
appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
textEncoderModelPath = settings.value("textEncoderModelPath",
appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-BF16.gguf").toString();
ditModelPath = settings.value("ditModelPath", appDir + "/acestep.cpp/models/acestep-v15-turbo-Q8_0.gguf").toString();
vaeModelPath = settings.value("vaeModelPath", appDir + "/acestep.cpp/models/vae-BF16.gguf").toString();
-
- // Load low VRAM mode
- bool lowVram = settings.value("lowVramMode", false).toBool();
- aceStep->setLowVramMode(lowVram);
-
- // Load flash attention setting
- bool flashAttention = settings.value("flashAttention", false).toBool();
- aceStep->setFlashAttention(flashAttention);
}
void MainWindow::saveSettings()
@@ -197,21 +161,13 @@ void MainWindow::saveSettings()
// Save shuffle mode
settings.setValue("shuffleMode", shuffleMode);
- // Save volume setting
- settings.setValue("volume", ui->volumeSlider->value());
-
// Save path settings
+ settings.setValue("aceStepPath", aceStepPath);
settings.setValue("qwen3ModelPath", qwen3ModelPath);
settings.setValue("textEncoderModelPath", textEncoderModelPath);
settings.setValue("ditModelPath", ditModelPath);
settings.setValue("vaeModelPath", vaeModelPath);
- // Save low VRAM mode
- settings.setValue("lowVramMode", aceStep->isLowVramMode());
-
- // Save flash attention setting
- settings.setValue("flashAttention", aceStep->isFlashAttention());
-
settings.setValue("firstRun", false);
}
@@ -260,7 +216,7 @@ void MainWindow::updateControls()
ui->removeSongButton->setEnabled(hasSongs && ui->songListView->currentIndex().isValid());
}
-void MainWindow::onPlayButtonClicked()
+void MainWindow::on_playButton_clicked()
{
if (isPaused)
{
@@ -281,9 +237,9 @@ void MainWindow::onPlayButtonClicked()
updateControls();
}
-void MainWindow::onPauseButtonClicked()
+void MainWindow::on_pauseButton_clicked()
{
- if (isPlaying && !isPaused && audioPlayer->isPlaying())
+ if (isPlaying && !isPaused)
{
// Pause playback
audioPlayer->pause();
@@ -292,7 +248,7 @@ void MainWindow::onPauseButtonClicked()
}
}
-void MainWindow::onSkipButtonClicked()
+void MainWindow::on_skipButton_clicked()
{
if (isPlaying)
{
@@ -302,7 +258,7 @@ void MainWindow::onSkipButtonClicked()
}
}
-void MainWindow::onStopButtonClicked()
+void MainWindow::on_stopButton_clicked()
{
if (isPlaying)
{
@@ -316,7 +272,7 @@ void MainWindow::onStopButtonClicked()
}
}
-void MainWindow::onShuffleButtonClicked()
+void MainWindow::on_shuffleButton_clicked()
{
shuffleMode = ui->shuffleButton->isChecked();
updateControls();
@@ -326,13 +282,18 @@ void MainWindow::onShuffleButtonClicked()
ensureSongsInQueue();
}
-void MainWindow::onAddSongButtonClicked()
+void MainWindow::on_addSongButton_clicked()
{
SongDialog dialog(this);
if (dialog.exec() == QDialog::Accepted)
{
- SongItem newSong = dialog.getSong();
+ QString caption = dialog.getCaption();
+ QString lyrics = dialog.getLyrics();
+ QString vocalLanguage = dialog.getVocalLanguage();
+
+ SongItem newSong(caption, lyrics);
+ newSong.vocalLanguage = vocalLanguage;
songModel->addSong(newSong);
// Select the new item
@@ -341,18 +302,17 @@ void MainWindow::onAddSongButtonClicked()
}
}
-void MainWindow::onSongListViewDoubleClicked(const QModelIndex &index)
+void MainWindow::on_songListView_doubleClicked(const QModelIndex &index)
{
if (!index.isValid())
return;
- disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::onSongListViewDoubleClicked);
+ disconnect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
int row = index.row();
if (index.column() == 0)
{
- isPaused = false;
if (isPlaying)
{
audioPlayer->stop();
@@ -360,8 +320,8 @@ void MainWindow::onSongListViewDoubleClicked(const QModelIndex &index)
else
{
isPlaying = true;
+ updateControls();
}
- updateControls();
flushGenerationQueue();
ui->nowPlayingLabel->setText("Now Playing: Waiting for generation...");
@@ -372,16 +332,24 @@ void MainWindow::onSongListViewDoubleClicked(const QModelIndex &index)
{
SongItem song = songModel->getSong(row);
- SongDialog dialog(this, song);
+ SongDialog dialog(this, song.caption, song.lyrics, song.vocalLanguage);
if (dialog.exec() == QDialog::Accepted)
- songModel->updateSong(songModel->index(row, 1), dialog.getSong());
+ {
+ QString caption = dialog.getCaption();
+ QString lyrics = dialog.getLyrics();
+ QString vocalLanguage = dialog.getVocalLanguage();
+
+ songModel->setData(songModel->index(row, 1), caption, SongListModel::CaptionRole);
+ songModel->setData(songModel->index(row, 1), lyrics, SongListModel::LyricsRole);
+ songModel->setData(songModel->index(row, 1), vocalLanguage, SongListModel::VocalLanguageRole);
+ }
}
- connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::onSongListViewDoubleClicked);
+ connect(ui->songListView, &QTableView::doubleClicked, this, &MainWindow::on_songListView_doubleClicked);
}
-void MainWindow::onRemoveSongButtonClicked()
+void MainWindow::on_removeSongButton_clicked()
{
QModelIndex currentIndex = ui->songListView->currentIndex();
if (!currentIndex.isValid())
@@ -401,18 +369,17 @@ void MainWindow::onRemoveSongButtonClicked()
}
}
-void MainWindow::onAdvancedSettingsButtonClicked()
+void MainWindow::on_advancedSettingsButton_clicked()
{
AdvancedSettingsDialog dialog(this);
// Set current values
dialog.setJsonTemplate(jsonTemplate);
+ dialog.setAceStepPath(aceStepPath);
dialog.setQwen3ModelPath(qwen3ModelPath);
dialog.setTextEncoderModelPath(textEncoderModelPath);
dialog.setDiTModelPath(ditModelPath);
dialog.setVAEModelPath(vaeModelPath);
- dialog.setLowVramMode(aceStep->isLowVramMode());
- dialog.setFlashAttention(aceStep->isFlashAttention());
if (dialog.exec() == QDialog::Accepted)
{
@@ -427,20 +394,12 @@ void MainWindow::onAdvancedSettingsButtonClicked()
// Update settings
jsonTemplate = dialog.getJsonTemplate();
+ aceStepPath = dialog.getAceStepPath();
qwen3ModelPath = dialog.getQwen3ModelPath();
textEncoderModelPath = dialog.getTextEncoderModelPath();
ditModelPath = dialog.getDiTModelPath();
vaeModelPath = dialog.getVAEModelPath();
- // Update model paths for acestep.cpp
- aceStep->setModelPaths(qwen3ModelPath, textEncoderModelPath, ditModelPath, vaeModelPath);
-
- // Update low VRAM mode
- aceStep->setLowVramMode(dialog.getLowVramMode());
-
- // Update flash attention setting
- aceStep->setFlashAttention(dialog.getFlashAttention());
-
saveSettings();
}
}
@@ -453,19 +412,11 @@ void MainWindow::playbackStarted()
void MainWindow::playSong(const SongItem& song)
{
currentSong = song;
- if (song.audioData)
- {
- audioPlayer->play(song.audioData);
- }
- else if (!song.file.isEmpty())
- {
- audioPlayer->play(song.file);
- }
+ audioPlayer->play(song.file);
songModel->setPlayingIndex(songModel->findSongIndexById(song.uniqueId));
ui->nowPlayingLabel->setText("Now Playing: " + song.caption);
ui->lyricsTextEdit->setPlainText(song.lyrics);
ui->jsonTextEdit->setPlainText(song.json);
- updateControls();
}
void MainWindow::songGenerated(const SongItem& song)
@@ -551,7 +502,7 @@ void MainWindow::updatePlaybackStatus(bool playing)
updateControls();
}
-void MainWindow::onPositionSliderSliderMoved(int position)
+void MainWindow::on_positionSlider_sliderMoved(int position)
{
if (isPlaying && audioPlayer->isPlaying())
{
@@ -559,11 +510,6 @@ void MainWindow::onPositionSliderSliderMoved(int position)
}
}
-void MainWindow::onVolumeSliderValueChanged(int value)
-{
- audioPlayer->setVolume(value);
-}
-
void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
{
// Only generate more songs if we're playing and not already at capacity
@@ -574,7 +520,7 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
SongItem lastSong;
SongItem workerSong;
- if(aceStep->isGenerating(&workerSong))
+ if(aceStep->isGenerateing(&workerSong))
lastSong = workerSong;
else if(!generatedSongQueue.empty())
lastSong = generatedSongQueue.last();
@@ -595,18 +541,21 @@ void MainWindow::ensureSongsInQueue(bool enqeueCurrent)
isGeneratingNext = true;
ui->statusbar->showMessage("Generateing: "+nextSong.caption);
- aceStep->requestGeneration(nextSong, jsonTemplate);
+ aceStep->requestGeneration(nextSong, jsonTemplate,
+ aceStepPath, qwen3ModelPath,
+ textEncoderModelPath, ditModelPath,
+ vaeModelPath);
}
void MainWindow::flushGenerationQueue()
{
generatedSongQueue.clear();
- aceStep->cancelGeneration();
+ aceStep->cancleGenerateion();
isGeneratingNext = false;
}
// Playlist save/load methods
-void MainWindow::onActionSavePlaylist()
+void MainWindow::on_actionSavePlaylist()
{
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/playlist.json",
@@ -618,7 +567,7 @@ void MainWindow::onActionSavePlaylist()
}
}
-void MainWindow::onActionLoadPlaylist()
+void MainWindow::on_actionLoadPlaylist()
{
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
@@ -631,7 +580,7 @@ void MainWindow::onActionLoadPlaylist()
}
}
-void MainWindow::onActionAppendPlaylist()
+void MainWindow::on_actionAppendPlaylist()
{
QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist",
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
@@ -642,7 +591,7 @@ void MainWindow::onActionAppendPlaylist()
}
}
-void MainWindow::onActionSaveSong()
+void MainWindow::on_actionSaveSong()
{
QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist",
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/song.json",
@@ -666,22 +615,7 @@ void MainWindow::onActionSaveSong()
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return;
-
- // Save audio from memory if available, otherwise fall back to file
- if (currentSong.audioData)
- {
- QFile wavFile(filePath + ".wav");
- if (wavFile.open(QIODevice::WriteOnly))
- {
- wavFile.write(*currentSong.audioData);
- wavFile.close();
- }
- }
- else if (!currentSong.file.isEmpty())
- {
- QFile::copy(currentSong.file, filePath + ".wav");
- }
-
+ QFile::copy(currentSong.file, filePath + ".wav");
file.write(jsonData);
file.close();
}
@@ -758,13 +692,16 @@ bool MainWindow::savePlaylistToJson(const QString &filePath, const QList(song.uniqueId); // Store as qint64 for JSON compatibility
songsArray.append(songObj);
}
QJsonObject rootObj;
rootObj["songs"] = songsArray;
- rootObj["version"] = "1.1";
+ rootObj["version"] = "1.0";
QJsonDocument doc(rootObj);
QByteArray jsonData = doc.toJson();
@@ -813,7 +750,8 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList &
QJsonObject rootObj = doc.object();
- if (rootObj.contains("version") && rootObj["version"].toString() != "1.0" && rootObj["version"].toString() != "1.1")
+ // Check for version compatibility
+ if (rootObj.contains("version") && rootObj["version"].toString() != "1.0")
{
qWarning() << "Unsupported playlist version:" << rootObj["version"].toString();
return false;
@@ -835,7 +773,35 @@ bool MainWindow::loadPlaylistFromJson(const QString &filePath, QList &
continue;
QJsonObject songObj = value.toObject();
- SongItem song(songObj);
+ SongItem song;
+
+ if (songObj.contains("caption"))
+ {
+ song.caption = songObj["caption"].toString();
+ }
+
+ if (songObj.contains("lyrics"))
+ {
+ song.lyrics = songObj["lyrics"].toString();
+ }
+
+ // Load vocalLanguage if present
+ if (songObj.contains("vocalLanguage"))
+ {
+ song.vocalLanguage = songObj["vocalLanguage"].toString();
+ }
+
+ // Load uniqueId if present (for backward compatibility)
+ if (songObj.contains("uniqueId"))
+ {
+ song.uniqueId = static_cast(songObj["uniqueId"].toInteger());
+ }
+ else
+ {
+ // Generate new ID for old playlists without uniqueId
+ song.uniqueId = QRandomGenerator::global()->generate64();
+ }
+
songs.append(song);
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 25cdf9d..54136f7 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,8 +1,3 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
@@ -36,7 +31,7 @@ class MainWindow : public QMainWindow
SongListModel *songModel;
AudioPlayer *audioPlayer;
QThread aceThread;
- AceStepWorker *aceStep;
+ AceStep *aceStep;
QTimer *playbackTimer;
QString formatTime(int milliseconds);
@@ -50,6 +45,7 @@ class MainWindow : public QMainWindow
QString jsonTemplate;
// Path settings
+ QString aceStepPath;
QString qwen3ModelPath;
QString textEncoderModelPath;
QString ditModelPath;
@@ -67,20 +63,19 @@ public slots:
void show();
private slots:
- void onPlayButtonClicked();
- void onPauseButtonClicked();
- void onSkipButtonClicked();
- void onStopButtonClicked();
- void onShuffleButtonClicked();
- void onPositionSliderSliderMoved(int position);
- void onVolumeSliderValueChanged(int value);
+ void on_playButton_clicked();
+ void on_pauseButton_clicked();
+ void on_skipButton_clicked();
+ void on_stopButton_clicked();
+ void on_shuffleButton_clicked();
+ void on_positionSlider_sliderMoved(int position);
void updatePosition(int position);
void updateDuration(int duration);
- void onAddSongButtonClicked();
- void onRemoveSongButtonClicked();
- void onAdvancedSettingsButtonClicked();
+ void on_addSongButton_clicked();
+ void on_removeSongButton_clicked();
+ void on_advancedSettingsButton_clicked();
- void onSongListViewDoubleClicked(const QModelIndex &index);
+ void on_songListView_doubleClicked(const QModelIndex &index);
void songGenerated(const SongItem& song);
void generationCanceld(const SongItem& song);
@@ -89,10 +84,10 @@ private slots:
void updatePlaybackStatus(bool playing);
void generationError(const QString &error);
- void onActionSavePlaylist();
- void onActionLoadPlaylist();
- void onActionAppendPlaylist();
- void onActionSaveSong();
+ void on_actionSavePlaylist();
+ void on_actionLoadPlaylist();
+ void on_actionAppendPlaylist();
+ void on_actionSaveSong();
private:
void loadSettings();
diff --git a/src/MainWindow.ui b/src/MainWindow.ui
index 37a6f78..828189b 100644
--- a/src/MainWindow.ui
+++ b/src/MainWindow.ui
@@ -178,9 +178,6 @@
-
-
- false
-
Pause
@@ -222,56 +219,14 @@
- -
-
-
- Qt::Orientation::Horizontal
-
-
-
- 15
- 20
-
-
-
-
- -
-
-
- Volume:
-
-
-
- -
-
-
-
- 100
- 20
-
-
-
- 100
-
-
- 50
-
-
- Qt::Orientation::Horizontal
-
-
-
-
Qt::Orientation::Horizontal
-
- QSizePolicy::Policy::Fixed
-
- 35
+ 40
20
diff --git a/src/SongDialog.cpp b/src/SongDialog.cpp
index e0ebde8..baa9ddc 100644
--- a/src/SongDialog.cpp
+++ b/src/SongDialog.cpp
@@ -1,142 +1,48 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "SongDialog.h"
#include "ui_SongDialog.h"
#include
-SongDialog::SongDialog(QWidget *parent, const SongItem &song)
+SongDialog::SongDialog(QWidget *parent, const QString &caption, const QString &lyrics, const QString &vocalLanguage)
: QDialog(parent),
- song(song),
- ui(new Ui::SongDialog)
+ ui(new Ui::SongDialog)
{
ui->setupUi(this);
- // Connect signals and slots explicitly
- connect(ui->okButton, &QPushButton::clicked, this, &SongDialog::onOkButtonClicked);
- connect(ui->cancelButton, &QPushButton::clicked, this, &SongDialog::onCancelButtonClicked);
-
- ui->captionEdit->setPlainText(song.caption);
- ui->lyricsEdit->setPlainText(song.lyrics);
- ui->checkBoxEnhanceCaption->setChecked(song.cotCaption);
+ // Set initial values if provided
+ if (!caption.isEmpty())
+ {
+ ui->captionEdit->setPlainText(caption);
+ }
+ if (!lyrics.isEmpty())
+ {
+ ui->lyricsEdit->setPlainText(lyrics);
+ }
// Setup vocal language combo box
- ui->vocalLanguageCombo->addItem("--", "");
+ ui->vocalLanguageCombo->addItem("--", ""); // Unset
ui->vocalLanguageCombo->addItem("English (en)", "en");
- ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh");
- ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja");
- ui->vocalLanguageCombo->addItem("Korean (ko)", "ko");
- ui->vocalLanguageCombo->addItem("Spanish (es)", "es");
- ui->vocalLanguageCombo->addItem("French (fr)", "fr");
ui->vocalLanguageCombo->addItem("German (de)", "de");
+ ui->vocalLanguageCombo->addItem("French (fr)", "fr");
+ ui->vocalLanguageCombo->addItem("Spanish (es)", "es");
+ ui->vocalLanguageCombo->addItem("Japanese (ja)", "ja");
+ ui->vocalLanguageCombo->addItem("Chinese (zh)", "zh");
+ ui->vocalLanguageCombo->addItem("Italian (it)", "it");
ui->vocalLanguageCombo->addItem("Portuguese (pt)", "pt");
ui->vocalLanguageCombo->addItem("Russian (ru)", "ru");
- ui->vocalLanguageCombo->addItem("Italian (it)", "it");
- ui->vocalLanguageCombo->addItem("Arabic (ar)", "ar");
- ui->vocalLanguageCombo->addItem("Azerbaijani (az)", "az");
- ui->vocalLanguageCombo->addItem("Bulgarian (bg)", "bg");
- ui->vocalLanguageCombo->addItem("Bengali (bn)", "bn");
- ui->vocalLanguageCombo->addItem("Catalan (ca)", "ca");
- ui->vocalLanguageCombo->addItem("Czech (cs)", "cs");
- ui->vocalLanguageCombo->addItem("Danish (da)", "da");
- ui->vocalLanguageCombo->addItem("Greek (el)", "el");
- ui->vocalLanguageCombo->addItem("Persian (fa)", "fa");
- ui->vocalLanguageCombo->addItem("Finnish (fi)", "fi");
- ui->vocalLanguageCombo->addItem("Hebrew (he)", "he");
- ui->vocalLanguageCombo->addItem("Hindi (hi)", "hi");
- ui->vocalLanguageCombo->addItem("Croatian (hr)", "hr");
- ui->vocalLanguageCombo->addItem("Haitian Creole (ht)", "ht");
- ui->vocalLanguageCombo->addItem("Hungarian (hu)", "hu");
- ui->vocalLanguageCombo->addItem("Indonesian (id)", "id");
- ui->vocalLanguageCombo->addItem("Icelandic (is)", "is");
- ui->vocalLanguageCombo->addItem("Latin (la)", "la");
- ui->vocalLanguageCombo->addItem("Lithuanian (lt)", "lt");
- ui->vocalLanguageCombo->addItem("Malay (ms)", "ms");
- ui->vocalLanguageCombo->addItem("Nepali (ne)", "ne");
- ui->vocalLanguageCombo->addItem("Dutch (nl)", "nl");
- ui->vocalLanguageCombo->addItem("Norwegian (no)", "no");
- ui->vocalLanguageCombo->addItem("Punjabi (pa)", "pa");
- ui->vocalLanguageCombo->addItem("Polish (pl)", "pl");
- ui->vocalLanguageCombo->addItem("Romanian (ro)", "ro");
- ui->vocalLanguageCombo->addItem("Sanskrit (sa)", "sa");
- ui->vocalLanguageCombo->addItem("Slovak (sk)", "sk");
- ui->vocalLanguageCombo->addItem("Serbian (sr)", "sr");
- ui->vocalLanguageCombo->addItem("Swedish (sv)", "sv");
- ui->vocalLanguageCombo->addItem("Swahili (sw)", "sw");
- ui->vocalLanguageCombo->addItem("Tamil (ta)", "ta");
- ui->vocalLanguageCombo->addItem("Telugu (te)", "te");
- ui->vocalLanguageCombo->addItem("Thai (th)", "th");
- ui->vocalLanguageCombo->addItem("Tagalog (tl)", "tl");
- ui->vocalLanguageCombo->addItem("Turkish (tr)", "tr");
- ui->vocalLanguageCombo->addItem("Ukrainian (uk)", "uk");
- ui->vocalLanguageCombo->addItem("Urdu (ur)", "ur");
- ui->vocalLanguageCombo->addItem("Vietnamese (vi)", "vi");
- ui->vocalLanguageCombo->addItem("Cantonese (yue)", "yue");
- ui->vocalLanguageCombo->addItem("Unknown", "unknown");
- ui->keyScaleCombo->addItem("--");
- ui->keyScaleCombo->addItem("C major");
- ui->keyScaleCombo->addItem("C# major");
- ui->keyScaleCombo->addItem("D major");
- ui->keyScaleCombo->addItem("D# major");
- ui->keyScaleCombo->addItem("E major");
- ui->keyScaleCombo->addItem("F major");
- ui->keyScaleCombo->addItem("F# major");
- ui->keyScaleCombo->addItem("G major");
- ui->keyScaleCombo->addItem("G# major");
- ui->keyScaleCombo->addItem("A major");
- ui->keyScaleCombo->addItem("A# major");
- ui->keyScaleCombo->addItem("B major");
- ui->keyScaleCombo->addItem("C minor");
- ui->keyScaleCombo->addItem("C# minor");
- ui->keyScaleCombo->addItem("D minor");
- ui->keyScaleCombo->addItem("D# minor");
- ui->keyScaleCombo->addItem("E minor");
- ui->keyScaleCombo->addItem("F minor");
- ui->keyScaleCombo->addItem("F# minor");
- ui->keyScaleCombo->addItem("G minor");
- ui->keyScaleCombo->addItem("G# minor");
- ui->keyScaleCombo->addItem("A minor");
- ui->keyScaleCombo->addItem("A# minor");
- ui->keyScaleCombo->addItem("B minor");
-
- if (!song.vocalLanguage.isEmpty())
+ // Set current language if provided
+ if (!vocalLanguage.isEmpty())
{
- int index = ui->vocalLanguageCombo->findData(song.vocalLanguage);
+ int index = ui->vocalLanguageCombo->findData(vocalLanguage);
if (index >= 0)
{
ui->vocalLanguageCombo->setCurrentIndex(index);
}
- else
- {
- ui->vocalLanguageCombo->addItem(song.vocalLanguage);
- ui->vocalLanguageCombo->setCurrentIndex(ui->vocalLanguageCombo->count()-1);
- }
}
else
{
- ui->vocalLanguageCombo->setCurrentIndex(0);
+ ui->vocalLanguageCombo->setCurrentIndex(0); // Default to unset
}
-
- if (!song.key.isEmpty())
- {
- int index = ui->keyScaleCombo->findText(song.key);
- if (index >= 0)
- {
- ui->keyScaleCombo->setCurrentIndex(index);
- }
- else
- {
- ui->keyScaleCombo->addItem(song.key);
- ui->keyScaleCombo->setCurrentIndex(ui->keyScaleCombo->count()-1);
- }
- }
- else
- {
- ui->keyScaleCombo->setCurrentIndex(0);
- }
-
- ui->bpmSpinBox->setValue(song.bpm);
}
SongDialog::~SongDialog()
@@ -144,10 +50,25 @@ SongDialog::~SongDialog()
delete ui;
}
-void SongDialog::onOkButtonClicked()
+QString SongDialog::getCaption() const
+{
+ return ui->captionEdit->toPlainText();
+}
+
+QString SongDialog::getLyrics() const
+{
+ return ui->lyricsEdit->toPlainText();
+}
+
+QString SongDialog::getVocalLanguage() const
+{
+ return ui->vocalLanguageCombo->currentData().toString();
+}
+
+void SongDialog::on_okButton_clicked()
{
// Validate that caption is not empty
- QString caption = ui->captionEdit->toPlainText();
+ QString caption = getCaption();
if (caption.trimmed().isEmpty())
{
QMessageBox::warning(this, "Invalid Input", "Caption cannot be empty.");
@@ -157,22 +78,7 @@ void SongDialog::onOkButtonClicked()
accept();
}
-void SongDialog::onCancelButtonClicked()
+void SongDialog::on_cancelButton_clicked()
{
reject();
-}
-
-const SongItem& SongDialog::getSong()
-{
- song.caption = ui->captionEdit->toPlainText();
- song.lyrics = ui->lyricsEdit->toPlainText();
- song.vocalLanguage = ui->vocalLanguageCombo->currentData().toString();
- song.cotCaption = ui->checkBoxEnhanceCaption->isChecked();
- if(ui->keyScaleCombo->currentIndex() > 0)
- song.key = ui->keyScaleCombo->currentText();
- else
- song.key = "";
- song.bpm = ui->bpmSpinBox->value();
- return song;
-}
-
+}
\ No newline at end of file
diff --git a/src/SongDialog.h b/src/SongDialog.h
index 5e4ce48..c8137f5 100644
--- a/src/SongDialog.h
+++ b/src/SongDialog.h
@@ -1,16 +1,9 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef SONGDIALOG_H
#define SONGDIALOG_H
#include
#include
-#include "SongItem.h"
-
namespace Ui
{
class SongDialog;
@@ -19,17 +12,19 @@ class SongDialog;
class SongDialog : public QDialog
{
Q_OBJECT
- SongItem song;
public:
- explicit SongDialog(QWidget *parent = nullptr, const SongItem& song = SongItem());
+ explicit SongDialog(QWidget *parent = nullptr, const QString &caption = "", const QString &lyrics = "",
+ const QString &vocalLanguage = "");
~SongDialog();
- const SongItem& getSong();
+ QString getCaption() const;
+ QString getLyrics() const;
+ QString getVocalLanguage() const;
private slots:
- void onOkButtonClicked();
- void onCancelButtonClicked();
+ void on_okButton_clicked();
+ void on_cancelButton_clicked();
private:
Ui::SongDialog *ui;
diff --git a/src/SongDialog.ui b/src/SongDialog.ui
index 56e0842..efedda8 100644
--- a/src/SongDialog.ui
+++ b/src/SongDialog.ui
@@ -1,157 +1,109 @@
-
- SongDialog
-
-
-
- 0
- 0
- 500
- 410
-
-
-
- Song Details
-
-
-
-
-
-
- 0
-
-
-
-
-
- Caption:
-
-
- Qt::AlignmentFlag::AlignTop
-
-
-
- -
-
-
- Enhance Caption
-
-
- true
-
-
-
-
-
- -
-
-
- -
-
-
- Lyrics (optional):
-
-
- Qt::AlignmentFlag::AlignTop
-
-
-
- -
-
-
- -
-
-
- 5
-
-
-
-
-
- Keyscale
-
-
-
- -
-
-
- true
-
-
-
- -
-
-
- Vocal Language:
-
-
- Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter
-
-
-
- -
-
-
- true
-
-
-
- -
-
-
- Bpm (0 for llm chooses)
-
-
-
- -
-
-
- 9999
-
-
-
-
-
- -
-
-
- 5
-
-
- 0
-
-
-
- -
-
-
-
-
-
- Qt::Orientation::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- OK
-
-
-
- -
-
-
- Cancel
-
-
-
-
-
-
-
-
-
-
+
+ SongDialog
+
+
+
+ 0
+ 0
+ 500
+ 400
+
+
+
+ Song Details
+
+
+ -
+
+
+ Caption:
+
+
+ Qt::AlignTop
+
+
+
+ -
+
+
+ Enter song caption (e.g., "Upbeat pop rock anthem with driving electric guitars")
+
+
+ 80
+
+
+
+ -
+
+
+ Lyrics (optional):
+
+
+ Qt::AlignTop
+
+
+
+ -
+
+
+ Enter lyrics or leave empty for instrumental music
+
+
+ 150
+
+
+
+ -
+
+
+ Vocal Language:
+
+
+ Qt::AlignTop
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ OK
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/SongItem.cpp b/src/SongItem.cpp
deleted file mode 100644
index 4b1686e..0000000
--- a/src/SongItem.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include "SongItem.h"
-
-SongItem::SongItem(const QString &caption, const QString &lyrics)
- : caption(caption), lyrics(lyrics), cotCaption(true), bpm(0)
-{
- uniqueId = QRandomGenerator::global()->generate64();
-}
-
-SongItem::SongItem(const QJsonObject& json)
-{
- load(json);
-}
-
-void SongItem::store(QJsonObject &json) const
-{
- if(!caption.isEmpty())
- json["caption"] = caption;
- if(!lyrics.isEmpty())
- json["lyrics"] = lyrics;
- json["unique_id"] = QString::number(uniqueId);
- json["use_cot_caption"] = cotCaption;
- if(!vocalLanguage.isEmpty())
- json["vocal_language"] = vocalLanguage;
- if(!key.isEmpty())
- json["keyscale"] = key;
- if(bpm != 0)
- json["bpm"] = static_cast(bpm);
-}
-
-void SongItem::load(const QJsonObject &json)
-{
- caption = json["caption"].toString();
- lyrics = json["lyrics"].toString();
- if(json.contains("unique_id"))
- uniqueId = json["unique_id"].toString().toULongLong();
- else
- uniqueId = QRandomGenerator::global()->generate64();
- cotCaption = json["use_cot_caption"].toBool(true);
- vocalLanguage = json["vocal_language"].toString();
- key = json["keyscale"].toString();
- bpm = json["bpm"].toInt(0);
-}
-
diff --git a/src/SongItem.h b/src/SongItem.h
index 0a80926..8a28891 100644
--- a/src/SongItem.h
+++ b/src/SongItem.h
@@ -1,33 +1,22 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#pragma once
#include
#include
#include
-#include
-#include
class SongItem
{
public:
QString caption;
QString lyrics;
- unsigned int bpm;
- QString key;
- QString vocalLanguage;
- bool cotCaption;
-
uint64_t uniqueId;
QString file;
+ QString vocalLanguage;
QString json;
- std::shared_ptr audioData;
- SongItem(const QString &caption = "", const QString &lyrics = "");
- SongItem(const QJsonObject& json);
-
- void store(QJsonObject& json) const;
- void load(const QJsonObject& json);
+ inline SongItem(const QString &caption = "", const QString &lyrics = "")
+ : caption(caption), lyrics(lyrics)
+ {
+ // Generate a unique ID using cryptographically secure random number
+ uniqueId = QRandomGenerator::global()->generate64();
+ }
};
diff --git a/src/SongListModel.cpp b/src/SongListModel.cpp
index 0b40f05..caa856a 100644
--- a/src/SongListModel.cpp
+++ b/src/SongListModel.cpp
@@ -1,6 +1,3 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "SongListModel.h"
#include
#include
@@ -111,26 +108,6 @@ bool SongListModel::setData(const QModelIndex &index, const QVariant &value, int
return true;
}
-void SongListModel::updateSong(const QModelIndex &index, const SongItem& song)
-{
- const SongItem &oldSong = songList[index.row()];
-
- if(song.caption != oldSong.caption)
- {
- emit dataChanged(index, index, {CaptionRole});
- }
- if(song.lyrics != oldSong.lyrics)
- {
- emit dataChanged(index, index, {LyricsRole});
- }
- if(song.vocalLanguage != oldSong.vocalLanguage)
- {
- emit dataChanged(index, index, {VocalLanguageRole});
- }
-
- songList[index.row()] = song;
-}
-
Qt::ItemFlags SongListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
diff --git a/src/SongListModel.h b/src/SongListModel.h
index bf1e22e..4a3275d 100644
--- a/src/SongListModel.h
+++ b/src/SongListModel.h
@@ -1,8 +1,3 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef SONGLISTMODEL_H
#define SONGLISTMODEL_H
@@ -37,7 +32,6 @@ public:
// Editable:
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
- void updateSong(const QModelIndex &index, const SongItem& song);
Qt::ItemFlags flags(const QModelIndex& index) const override;
// Add/remove songs
diff --git a/src/clickableslider.cpp b/src/clickableslider.cpp
index 63477aa..3006b2e 100644
--- a/src/clickableslider.cpp
+++ b/src/clickableslider.cpp
@@ -1,6 +1,3 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "clickableslider.h"
#include
diff --git a/src/clickableslider.h b/src/clickableslider.h
index 0d651e1..4b4e669 100644
--- a/src/clickableslider.h
+++ b/src/clickableslider.h
@@ -1,8 +1,3 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef CLICKABLESLIDER_H
#define CLICKABLESLIDER_H
diff --git a/src/elidedlabel.cpp b/src/elidedlabel.cpp
index 5830ed7..191a0fd 100644
--- a/src/elidedlabel.cpp
+++ b/src/elidedlabel.cpp
@@ -1,6 +1,3 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include "elidedlabel.h"
#include
diff --git a/src/elidedlabel.h b/src/elidedlabel.h
index 285a41c..76df007 100644
--- a/src/elidedlabel.h
+++ b/src/elidedlabel.h
@@ -1,8 +1,3 @@
-/*
- * Copyright Carl Philipp Klemm 2026
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
#ifndef ELIDEDLABEL_H
#define ELIDEDLABEL_H
diff --git a/src/main.cpp b/src/main.cpp
index f401fda..c51ac1f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,3 @@
-// Copyright Carl Philipp Klemm 2026
-// SPDX-License-Identifier: GPL-3.0-or-later
-
#include
#include
diff --git a/tests/test_acestep_worker.cpp b/tests/test_acestep_worker.cpp
deleted file mode 100644
index 60a5ffa..0000000
--- a/tests/test_acestep_worker.cpp
+++ /dev/null
@@ -1,525 +0,0 @@
-// Test for AceStepWorker
-// Compile with: cmake .. && make test_acestep_worker && ./test_acestep_worker
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../src/AceStepWorker.h"
-
-// Test result tracking
-static int testsPassed = 0;
-static int testsFailed = 0;
-static int testsSkipped = 0;
-
-#define TEST(name) void test_##name()
-#define RUN_TEST(name) do { \
- std::cout << "Running " << #name << "... "; \
- test_##name(); \
- if (test_skipped) { \
- std::cout << "SKIPPED" << std::endl; \
- testsSkipped++; \
- test_skipped = false; \
- } else if (test_failed) { \
- std::cout << "FAILED" << std::endl; \
- testsFailed++; \
- test_failed = false; \
- } else { \
- std::cout << "PASSED" << std::endl; \
- testsPassed++; \
- } \
-} while(0)
-
-static bool test_failed = false;
-static bool test_skipped = false;
-
-#define ASSERT_TRUE(cond) do { \
- if (!(cond)) { \
- std::cout << "FAILED: " << #cond << " at line " << __LINE__ << std::endl; \
- test_failed = true; \
- return; \
- } \
-} while(0)
-
-#define ASSERT_FALSE(cond) ASSERT_TRUE(!(cond))
-
-#define SKIP_IF(cond) do { \
- if (cond) { \
- std::cout << "(skipping: " << #cond << ") "; \
- test_skipped = true; \
- return; \
- } \
-} while(0)
-
-// Helper to get model paths from settings like main app
-struct ModelPaths {
- QString lmPath;
- QString textEncoderPath;
- QString ditPath;
- QString vaePath;
-};
-
-static ModelPaths getModelPathsFromSettings()
-{
- ModelPaths paths;
- QSettings settings("MusicGenerator", "AceStepGUI");
-
- QString appDir = QCoreApplication::applicationDirPath();
- paths.lmPath = settings.value("qwen3ModelPath",
- appDir + "/acestep.cpp/models/acestep-5Hz-lm-4B-Q8_0.gguf").toString();
- paths.textEncoderPath = settings.value("textEncoderModelPath",
- appDir + "/acestep.cpp/models/Qwen3-Embedding-0.6B-Q8_0.gguf").toString();
- paths.ditPath = settings.value("ditModelPath",
- appDir + "/acestep.cpp/models/acestep-v15-turbo-Q8_0.gguf").toString();
- paths.vaePath = settings.value("vaeModelPath",
- appDir + "/acestep.cpp/models/vae-BF16.gguf").toString();
-
- return paths;
-}
-
-static bool modelsExist(const ModelPaths& paths)
-{
- return QFileInfo::exists(paths.lmPath) &&
- QFileInfo::exists(paths.textEncoderPath) &&
- QFileInfo::exists(paths.ditPath) &&
- QFileInfo::exists(paths.vaePath);
-}
-
-// Test 1: Check that isGenerating returns false initially
-TEST(initialState)
-{
- AceStepWorker worker;
- ASSERT_TRUE(!worker.isGenerating());
-}
-
-// Test 2: Check that requestGeneration returns false when no model paths set
-TEST(noModelPaths)
-{
- AceStepWorker worker;
- SongItem song("test caption", "");
-
- bool result = worker.requestGeneration(song, "{}");
- ASSERT_FALSE(result);
- ASSERT_TRUE(!worker.isGenerating());
-}
-
-// Test 3: Check that setModelPaths stores paths correctly
-TEST(setModelPaths)
-{
- AceStepWorker worker;
- worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
- ASSERT_TRUE(true);
-}
-
-// Test 4: Check async behavior - requestGeneration returns immediately
-TEST(asyncReturnsImmediately)
-{
- AceStepWorker worker;
- worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
-
- SongItem song("test caption", "");
-
- // If this blocks, the test will hang
- bool result = worker.requestGeneration(song, "{}");
-
- // Should return false due to invalid paths, but immediately
- ASSERT_FALSE(result);
-}
-
-// Test 5: Check that cancelGeneration sets the cancel flag
-TEST(cancellationFlag)
-{
- AceStepWorker worker;
- worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
- worker.cancelGeneration();
- ASSERT_TRUE(true);
-}
-
-// Test 6: Check that signals are defined correctly
-TEST(signalsExist)
-{
- AceStepWorker worker;
-
- // Verify signals exist by connecting to them (compile-time check)
- QObject::connect(&worker, &AceStepWorker::songGenerated, [](const SongItem&) {});
- QObject::connect(&worker, &AceStepWorker::generationCanceled, [](const SongItem&) {});
- QObject::connect(&worker, &AceStepWorker::generationError, [](const QString&) {});
- QObject::connect(&worker, &AceStepWorker::progressUpdate, [](int) {});
-
- ASSERT_TRUE(true);
-}
-
-// Test 7: Check SongItem to AceRequest conversion (internal)
-TEST(requestConversion)
-{
- AceStepWorker worker;
-
- SongItem song("Upbeat pop rock", "[Verse 1]");
- song.cotCaption = true;
-
- QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
-
- worker.setModelPaths("/path/lm.gguf", "/path/encoder.gguf", "/path/dit.gguf", "/path/vae.gguf");
- bool result = worker.requestGeneration(song, templateJson);
-
- // Should fail due to invalid paths, but shouldn't crash
- ASSERT_FALSE(result);
-}
-
-// Test 8: Read model paths from settings
-TEST(readSettings)
-{
- ModelPaths paths = getModelPathsFromSettings();
-
- std::cout << "\n Model paths from settings:" << std::endl;
- std::cout << " LM: " << paths.lmPath.toStdString() << std::endl;
- std::cout << " Text Encoder: " << paths.textEncoderPath.toStdString() << std::endl;
- std::cout << " DiT: " << paths.ditPath.toStdString() << std::endl;
- std::cout << " VAE: " << paths.vaePath.toStdString() << std::endl;
-
- ASSERT_TRUE(!paths.lmPath.isEmpty());
- ASSERT_TRUE(!paths.textEncoderPath.isEmpty());
- ASSERT_TRUE(!paths.ditPath.isEmpty());
- ASSERT_TRUE(!paths.vaePath.isEmpty());
-}
-
-// Test 9: Check if model files exist
-TEST(checkModelFiles)
-{
- ModelPaths paths = getModelPathsFromSettings();
-
- bool lmExists = QFileInfo::exists(paths.lmPath);
- bool encoderExists = QFileInfo::exists(paths.textEncoderPath);
- bool ditExists = QFileInfo::exists(paths.ditPath);
- bool vaeExists = QFileInfo::exists(paths.vaePath);
-
- std::cout << "\n Model file status:" << std::endl;
- std::cout << " LM: " << (lmExists ? "EXISTS" : "MISSING") << std::endl;
- std::cout << " Text Encoder: " << (encoderExists ? "EXISTS" : "MISSING") << std::endl;
- std::cout << " DiT: " << (ditExists ? "EXISTS" : "MISSING") << std::endl;
- std::cout << " VAE: " << (vaeExists ? "EXISTS" : "MISSING") << std::endl;
-
- ASSERT_TRUE(lmExists);
- ASSERT_TRUE(encoderExists);
- ASSERT_TRUE(ditExists);
- ASSERT_TRUE(vaeExists);
-}
-
-// Test 10: Actually generate a song (requires valid model paths)
-TEST(generateSong)
-{
- ModelPaths paths = getModelPathsFromSettings();
-
- // Skip if models don't exist
- SKIP_IF(!modelsExist(paths));
-
- AceStepWorker worker;
- worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
-
- SongItem song("Upbeat pop rock with driving guitars", "");
-
- QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
-
- // Track if we get progress updates
- bool gotProgress = false;
- QObject::connect(&worker, &AceStepWorker::progressUpdate, [&gotProgress](int p) {
- std::cout << "\n Progress: " << p << "%" << std::endl;
- gotProgress = true;
- });
-
- // Track generation result
- bool generationCompleted = false;
- SongItem resultSong;
- QObject::connect(&worker, &AceStepWorker::songGenerated,
- [&generationCompleted, &resultSong](const SongItem& song) {
- std::cout << "\n Song generated successfully!" << std::endl;
- std::cout << " Caption: " << song.caption.toStdString() << std::endl;
- std::cout << " Lyrics: " << song.lyrics.left(100).toStdString() << "..." << std::endl;
- std::cout << " File: " << song.file.toStdString() << std::endl;
- resultSong = song;
- generationCompleted = true;
- });
-
- QString errorMsg;
- QObject::connect(&worker, &AceStepWorker::generationError,
- [&errorMsg](const QString& err) {
- std::cout << "\n Error: " << err.toStdString() << std::endl;
- errorMsg = err;
- });
-
- std::cout << "\n Starting generation..." << std::endl;
-
- // Request generation
- bool result = worker.requestGeneration(song, templateJson);
- ASSERT_TRUE(result);
-
- // Use QEventLoop with timer for proper event processing
- QEventLoop loop;
- QTimer timeoutTimer;
-
- timeoutTimer.setSingleShot(true);
- timeoutTimer.start(300000); // 5 minute timeout
-
- QObject::connect(&worker, &AceStepWorker::songGenerated, &loop, &QEventLoop::quit);
- QObject::connect(&worker, &AceStepWorker::generationError, &loop, &QEventLoop::quit);
- QObject::connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
-
- loop.exec();
-
- ASSERT_TRUE(generationCompleted);
- ASSERT_TRUE(resultSong.audioData != nullptr);
- ASSERT_TRUE(!resultSong.audioData->isEmpty());
-
- // Check audio data is not empty
- std::cout << " Audio data size: " << resultSong.audioData->size() << " bytes" << std::endl;
- ASSERT_TRUE(resultSong.audioData->size() > 1000); // Should be at least 1KB for valid audio
-}
-
-// Test 11: Test cancellation
-TEST(cancellation)
-{
- ModelPaths paths = getModelPathsFromSettings();
-
- // Skip if models don't exist
- SKIP_IF(!modelsExist(paths));
-
- AceStepWorker worker;
- worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
-
- SongItem song("A very long ambient piece", "");
-
- QString templateJson = R"({"inference_steps": 50, "shift": 3.0, "vocal_language": "en"})";
-
- bool cancelReceived = false;
- QObject::connect(&worker, &AceStepWorker::generationCanceled,
- [&cancelReceived](const SongItem&) {
- std::cout << "\n Generation was canceled!" << std::endl;
- cancelReceived = true;
- });
-
- std::cout << "\n Starting generation and will cancel after 2 seconds..." << std::endl;
-
- // Start generation
- bool result = worker.requestGeneration(song, templateJson);
- ASSERT_TRUE(result);
-
- // Wait 2 seconds then cancel
- QThread::sleep(2);
- worker.cancelGeneration();
-
- // Wait a bit for cancellation to be processed
- QThread::sleep(1);
- QCoreApplication::processEvents();
-
- // Note: cancellation may or may not complete depending on where in the process
- // the cancel was requested. The important thing is it doesn't crash.
- std::cout << " Cancel requested, no crash detected" << std::endl;
- ASSERT_TRUE(true);
-}
-
-// Test 12: Test low VRAM mode generation
-TEST(generateSongLowVram)
-{
- ModelPaths paths = getModelPathsFromSettings();
-
- // Skip if models don't exist
- SKIP_IF(!modelsExist(paths));
-
- AceStepWorker worker;
- worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
- worker.setLowVramMode(true);
-
- ASSERT_TRUE(worker.isLowVramMode());
-
- SongItem song("Chill electronic music", "");
-
- QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
-
- // Track generation result
- bool generationCompleted = false;
- SongItem resultSong;
- QObject::connect(&worker, &AceStepWorker::songGenerated,
- [&generationCompleted, &resultSong](const SongItem& song) {
- std::cout << "\n Low VRAM mode: Song generated successfully!" << std::endl;
- std::cout << " Caption: " << song.caption.toStdString() << std::endl;
- if (song.audioData) {
- std::cout << " Audio data size: " << song.audioData->size() << " bytes" << std::endl;
- } else {
- std::cout << " Audio data size: null" << std::endl;
- }
- resultSong = song;
- generationCompleted = true;
- });
-
- QString errorMsg;
- QObject::connect(&worker, &AceStepWorker::generationError,
- [&errorMsg](const QString& err) {
- std::cout << "\n Error: " << err.toStdString() << std::endl;
- errorMsg = err;
- });
-
- std::cout << "\n Starting low VRAM mode generation..." << std::endl;
-
- // Request generation
- bool result = worker.requestGeneration(song, templateJson);
- ASSERT_TRUE(result);
-
- // Use QEventLoop with timer for proper event processing
- QEventLoop loop;
- QTimer timeoutTimer;
-
- timeoutTimer.setSingleShot(true);
- timeoutTimer.start(300000); // 5 minute timeout
-
- QObject::connect(&worker, &AceStepWorker::songGenerated, &loop, &QEventLoop::quit);
- QObject::connect(&worker, &AceStepWorker::generationError, &loop, &QEventLoop::quit);
- QObject::connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
-
- loop.exec();
-
- ASSERT_TRUE(generationCompleted);
- ASSERT_TRUE(resultSong.audioData != nullptr);
- ASSERT_TRUE(!resultSong.audioData->isEmpty());
-
- std::cout << " Audio data size: " << resultSong.audioData->size() << " bytes" << std::endl;
- ASSERT_TRUE(resultSong.audioData->size() > 1000);
-}
-
-// Test 13: Test normal mode keeps models loaded between generations
-TEST(normalModeKeepsModelsLoaded)
-{
- ModelPaths paths = getModelPathsFromSettings();
-
- // Skip if models don't exist
- SKIP_IF(!modelsExist(paths));
-
- AceStepWorker worker;
- worker.setModelPaths(paths.lmPath, paths.textEncoderPath, paths.ditPath, paths.vaePath);
- // Normal mode is default (lowVramMode = false)
-
- ASSERT_FALSE(worker.isLowVramMode());
-
- QString templateJson = R"({"inference_steps": 8, "shift": 3.0, "vocal_language": "en"})";
-
- // Generate first song
- bool firstGenerationCompleted = false;
- QObject::connect(&worker, &AceStepWorker::songGenerated,
- [&firstGenerationCompleted](const SongItem&) {
- firstGenerationCompleted = true;
- });
-
- QObject::connect(&worker, &AceStepWorker::generationError,
- [](const QString& err) {
- std::cout << "\n Error: " << err.toStdString() << std::endl;
- });
-
- std::cout << "\n Generating first song (normal mode)..." << std::endl;
-
- SongItem song1("First song", "");
- bool result = worker.requestGeneration(song1, templateJson);
- ASSERT_TRUE(result);
-
- QEventLoop loop;
- QTimer timeoutTimer;
- timeoutTimer.setSingleShot(true);
- timeoutTimer.start(300000);
-
- QObject::connect(&worker, &AceStepWorker::songGenerated, &loop, &QEventLoop::quit);
- QObject::connect(&worker, &AceStepWorker::generationError, &loop, &QEventLoop::quit);
- QObject::connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
-
- loop.exec();
-
- ASSERT_TRUE(firstGenerationCompleted);
- std::cout << " First generation completed, models should still be loaded" << std::endl;
-
- // Generate second song - in normal mode this should be faster since models are already loaded
- bool secondGenerationCompleted = false;
- SongItem secondResult;
- QObject::connect(&worker, &AceStepWorker::songGenerated,
- [&secondGenerationCompleted, &secondResult](const SongItem& song) {
- secondGenerationCompleted = true;
- secondResult = song;
- });
-
- std::cout << " Generating second song (should use cached models)..." << std::endl;
-
- SongItem song2("Second song", "");
- result = worker.requestGeneration(song2, templateJson);
- ASSERT_TRUE(result);
-
- QEventLoop loop2;
- QTimer timeoutTimer2;
- timeoutTimer2.setSingleShot(true);
- timeoutTimer2.start(300000);
-
- QObject::connect(&worker, &AceStepWorker::songGenerated, &loop2, &QEventLoop::quit);
- QObject::connect(&worker, &AceStepWorker::generationError, &loop2, &QEventLoop::quit);
- QObject::connect(&timeoutTimer2, &QTimer::timeout, &loop2, &QEventLoop::quit);
-
- loop2.exec();
-
- ASSERT_TRUE(secondGenerationCompleted);
- ASSERT_TRUE(secondResult.audioData != nullptr);
- ASSERT_TRUE(!secondResult.audioData->isEmpty());
-
- std::cout << " Second generation completed successfully" << std::endl;
- std::cout << " Audio data size: " << secondResult.audioData->size() << " bytes" << std::endl;
-}
-
-// Test 14: Test setLowVramMode toggle
-TEST(lowVramModeToggle)
-{
- AceStepWorker worker;
-
- // Default should be false (normal mode)
- ASSERT_FALSE(worker.isLowVramMode());
-
- // Enable low VRAM mode
- worker.setLowVramMode(true);
- ASSERT_TRUE(worker.isLowVramMode());
-
- // Disable low VRAM mode
- worker.setLowVramMode(false);
- ASSERT_FALSE(worker.isLowVramMode());
-
- // Toggle again
- worker.setLowVramMode(true);
- ASSERT_TRUE(worker.isLowVramMode());
-}
-
-int main(int argc, char *argv[])
-{
- QCoreApplication app(argc, argv);
-
- std::cout << "=== AceStepWorker Tests ===" << std::endl;
-
- RUN_TEST(initialState);
- RUN_TEST(noModelPaths);
- RUN_TEST(setModelPaths);
- RUN_TEST(asyncReturnsImmediately);
- RUN_TEST(cancellationFlag);
- RUN_TEST(signalsExist);
- RUN_TEST(requestConversion);
- RUN_TEST(readSettings);
- RUN_TEST(checkModelFiles);
- RUN_TEST(generateSong);
- RUN_TEST(cancellation);
- RUN_TEST(generateSongLowVram);
- RUN_TEST(normalModeKeepsModelsLoaded);
- RUN_TEST(lowVramModeToggle);
-
- std::cout << "\n=== Results ===" << std::endl;
- std::cout << "Passed: " << testsPassed << std::endl;
- std::cout << "Skipped: " << testsSkipped << std::endl;
- std::cout << "Failed: " << testsFailed << std::endl;
-
- return testsFailed > 0 ? 1 : 0;
-}
\ No newline at end of file
diff --git a/third_party/acestep.cpp b/third_party/acestep.cpp
deleted file mode 160000
index d28398d..0000000
--- a/third_party/acestep.cpp
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit d28398db0ffdb77e8ae071ff31bde8c559e7085a