diff --git a/.github/ISSUE_TEMPLATE/issue-template.md b/.github/ISSUE_TEMPLATE/issue-template.md index 73ec7631..7e6a909c 100644 --- a/.github/ISSUE_TEMPLATE/issue-template.md +++ b/.github/ISSUE_TEMPLATE/issue-template.md @@ -1,8 +1,7 @@ --- name: Issue template about: Create issue from template -title: "[BUG] - short description of your issue" -labels: bug +title: "[BUG/FEATURE/OTHER] - short description of your issue" assignees: '' --- @@ -10,7 +9,7 @@ assignees: '' Thank you for helping to improve Little Piggy Tracker by making this report. #### All feature requests made here will be treated as a request from the reporter to implement said feature. -When creating issues please provide the following items: +When creating issues please provide the following items where applicable: - Attach or paste contents of **lgpt.log** of when the issue occurred - Declare **platform configuration** such as desktop/console, OS version and audio configuration if applicable diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e308829b..aad9c03b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,26 +8,28 @@ All PR:s are run through line-by-line clang-format and builds for all supported clang-format can be applied locally on commit by installing pre-commit from repo root: cp pre-commit .git/hooks/ -Fixes # (issue) - +In section below, delete any option that isn't relevant. ## Type of change - -Please delete option that isn't relevant. +- Fixes #(issue number) +- Feature - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) # How Has This Been Tested? -Please describe the tests that you ran to verify your changes. Provide instructions on how to reproduce. +Please describe the tests procedure by which you verified your changes. +Provide instructions on how to reproduce. **Test Configuration**: * Hardware: -* OS version: +* Test steps: # Checklist: - [ ] I have performed a self-review of my code - [ ] I have commented particularly in hard-to-understand areas -- [ ] I have updated documentation regarding my changes -- [ ] My changes generate no new warnings +- [ ] I have updated CHANGELOG +- [ ] I have updated docs/wiki/What-is-LittlePiggyTracker.md reflecting my changes +- [ ] I have version bumped in `sources/Application/Model/Project.h` +- [ ] My changes generate no new warnings (build without your change then apply your change to check this) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb66de3e..5997209c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: run: | python -m pip install --upgrade pip pip install Pillow - choco install -y directx-sdk zip --no-progress --yes + choco install -y directx-sdk zip ffmpeg-shared --no-progress --yes shell: powershell - name: Download and Extract VCE9 release @@ -47,7 +47,7 @@ jobs: shell: bash working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -86,7 +86,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -125,7 +125,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -154,7 +154,7 @@ jobs: - name: Install required libraries run: | sudo apt update - sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow + sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow libavcodec-dev libavformat-dev libavfilter-dev libavutil-dev libswresample-dev - name: Build X64 working-directory: projects @@ -163,7 +163,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -208,7 +208,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -248,7 +248,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -288,7 +288,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -326,9 +326,13 @@ jobs: wget -qO ./debian-archive-keyring.deb "$BASE$LATEST" dpkg -i ./debian-archive-keyring.deb - apt update && apt install -y python3 python3-pillow + apt update && apt install -y python3 python3-pillow cd projects make PLATFORM=GARLIC + # copy libav deps to the .zip + echo copy files from usr/lib to lib + mkdir lib && find /opt/miyoo -name "*libav*so*" -exec cp -P {} lib/ \; + ls -la lib ' sudo chmod -R 777 ./workspace/projects sudo chown -R root:root ./workspace/projects @@ -336,7 +340,7 @@ jobs: - name: Package build working-directory: ./workspace/projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -366,7 +370,7 @@ jobs: - name: Install Garlic Plus toolchain run: | sudo apt update && sudo apt install -y python3-pillow - wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/simotek/union-rg35xxplus-toolchain/releases/download/20240830/rg35xxplus-toolchain.tar.xz + wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/djdiskmachine/union-rg35xxplus-toolchain/releases/download/1.1/rg35xxplus-toolchain.tar.gz mkdir /opt/rg35xxplus-toolchain tar -xvf /tmp/rg35xxplus-toolchain.tar.xz -C /opt/rg35xxplus-toolchain --strip-components=1 @@ -377,7 +381,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -418,7 +422,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -449,6 +453,7 @@ jobs: wget https://www.libsdl.org/release/SDL2-2.0.14.dmg hdiutil attach SDL2-2.0.14.dmg sudo cp -R /Volumes/SDL2/SDL2.framework /Library/Frameworks/ + brew install ffmpeg - name: Build Xcode project run: | @@ -456,15 +461,22 @@ jobs: cp -R bin/LittleGPTracker.app projects/ - name: Package build - working-directory: projects + working-directory: projects/resources/packaging shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) - curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip + TAG=$(curl -fsSL \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/djdiskmachine/lgpt-resources/tags) + echo TAG==$TAG + TAG=$(echo $TAG | jq -r '.[0].name') + echo TAG==$TAG + curl -fL -o lgpt-resources.zip \ + https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip - mv lgpt-resources-${TAG}/*/ ./resources/packaging - rm -rf lgpt-resources* - ./resources/packaging/lgpt_package.sh + ./lgpt_package.sh - name: Extract Git tag name id: extract_tag diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0a8a06c8..af47032a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -9,14 +9,12 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - - name: Fetch all branches - run: git fetch + with: + fetch-depth: 0 - name: Check formatting run: | - git fetch origin master:master - CHANGED_FILES=$(git diff --name-only master --diff-filter=ACM | grep -iE '\.(cpp|h|c|hpp)$') + CHANGED_FILES=$(git diff --name-only origin/master...HEAD --diff-filter=ACM | grep -iE '\.(cpp|h|c|hpp)$' || true) if [ -z "$CHANGED_FILES" ]; then echo "No C/C++ files changed." @@ -28,7 +26,7 @@ jobs: # Run clang-format on the changed lines of each staged file for FILE in $CHANGED_FILES; do # Get the changed lines in the file - CHANGED_LINES=$(git diff -U0 master -- $FILE | grep -E '^@@' | awk '{print $3}' | cut -d',' -f1) + CHANGED_LINES=$(git diff -U0 origin/master -- $FILE | grep -E '^@@' | awk '{print $3}' | cut -d',' -f1) # Format each changed line in the file for LINE in $CHANGED_LINES; do @@ -60,8 +58,12 @@ jobs: uses: actions/checkout@v4.1.7 - name: Install build deps - run: choco install -y directx-sdk zip --no-progress --yes + run: choco install -y directx-sdk zip ffmpeg-shared --no-progress --yes shell: powershell + + - name: Install FFmpeg development libraries + run: vcpkg install ffmpeg[avcodec,avformat,avfilter]:x86-windows-static + shell: cmd - name: Download and Extract VCE9 release run: | @@ -93,7 +95,7 @@ jobs: shell: bash working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -117,7 +119,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update - sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl1.2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow + sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl1.2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow libavcodec-dev:i386 libavformat-dev:i386 libavfilter-dev:i386 libavutil-dev:i386 libswresample-dev:i386 - name: Build DEB working-directory: projects @@ -126,7 +128,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -150,7 +152,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update - sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow + sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow libavcodec-dev:i386 libavformat-dev:i386 libavfilter-dev:i386 libavutil-dev:i386 libswresample-dev:i386 - name: Build X86 working-directory: projects @@ -159,7 +161,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -198,7 +200,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -232,7 +234,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -264,9 +266,13 @@ jobs: wget -qO ./debian-archive-keyring.deb "$BASE$LATEST" dpkg -i ./debian-archive-keyring.deb - apt update && apt install -y python3 python3-pillow + apt update && apt install -y python3 python3-pillow cd projects make PLATFORM=GARLIC + # copy libav deps to the .zip + echo copy files from usr/lib to lib + mkdir lib && find /opt/miyoo -name "*libav*so*" -exec cp -P {} lib/ \; + ls -la lib ' sudo chmod -R 777 ./workspace/projects sudo chown -R root:root ./workspace/projects @@ -274,7 +280,7 @@ jobs: - name: Package build working-directory: ./workspace/projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -298,7 +304,7 @@ jobs: - name: Install Garlic Plus toolchain run: | sudo apt update && sudo apt install -y python3-pillow - wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/simotek/union-rg35xxplus-toolchain/releases/download/20240830/rg35xxplus-toolchain.tar.xz + wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/djdiskmachine/union-rg35xxplus-toolchain/releases/download/1.1/rg35xxplus-toolchain.tar.gz mkdir /opt/rg35xxplus-toolchain tar -xvf /tmp/rg35xxplus-toolchain.tar.xz -C /opt/rg35xxplus-toolchain --strip-components=1 @@ -309,7 +315,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -344,7 +350,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -368,7 +374,7 @@ jobs: - name: Install Miyoo Mini toolchain run: | sudo apt update && sudo apt install -y python3-pillow - wget -O /tmp/miyoomini-toolchain.tar.xz https://github.com/djdiskmachine/miyoomini-toolchain-buildroot/releases/download/1.0.0/miyoomini-toolchain.tar.xz + wget -O /tmp/miyoomini-toolchain.tar.xz https://github.com/djdiskmachine/miyoomini-toolchain-buildroot/releases/download/1.1/miyoomini-toolchain.tar.xz mkdir /opt/miyoomini-toolchain tar -xvf /tmp/miyoomini-toolchain.tar.xz -C /opt/miyoomini-toolchain --strip-components=1 @@ -380,7 +386,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -404,7 +410,8 @@ jobs: - name: Install required libraries run: | sudo apt update - sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow + sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow libavcodec-dev libavformat-dev libavfilter-dev libavutil-dev libswresample-dev + - name: Build X64 working-directory: projects @@ -414,7 +421,7 @@ jobs: - name: Package build working-directory: projects run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) + TAG=$(curl "https://api.github.com/repos/djdiskmachine/lgpt-resources/tags" | jq -r '.[0].name') curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip mv lgpt-resources-${TAG}/*/ ./resources/packaging @@ -439,6 +446,7 @@ jobs: wget https://www.libsdl.org/release/SDL2-2.0.14.dmg hdiutil attach SDL2-2.0.14.dmg sudo cp -R /Volumes/SDL2/SDL2.framework /Library/Frameworks/ + brew install ffmpeg - name: Build Xcode project run: | @@ -446,15 +454,22 @@ jobs: cp -R bin/LittleGPTracker.app projects/ - name: Package build - working-directory: projects + working-directory: projects/resources/packaging shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - TAG=$(curl -s https://api.github.com/repos/djdiskmachine/lgpt-resources/releases/latest | grep '"tag_name"' | head -1 | cut -d'"' -f4) - curl -L -o lgpt-resources.zip https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip + TAG=$(curl -fsSL \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/djdiskmachine/lgpt-resources/tags) + echo TAG==$TAG + TAG=$(echo $TAG | jq -r '.[0].name') + echo TAG==$TAG + curl -fL -o lgpt-resources.zip \ + https://github.com/djdiskmachine/lgpt-resources/archive/refs/tags/${TAG}.zip unzip lgpt-resources.zip - mv lgpt-resources-${TAG}/*/ ./resources/packaging - rm -rf lgpt-resources* - ./resources/packaging/lgpt_package.sh + ./lgpt_package.sh - name: Upload artifact uses: actions/upload-artifact@v4 diff --git a/CHANGELOG b/CHANGELOG index 2be5d1f0..8fd20f48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,28 @@ +1.6.0-bacon4 + QWERTY keyboard name entry for new projects + * Press A on the project name field to enter QWERTY keyboard mode + * Navigate the on-screen keyboard with D-PAD/arrows + * Press A to input the selected character + * Press B to backspace + * Press L/R to move the text cursor left/right + * Press START or select OK to exit keyboard mode + * All keyboard navigation logic extracted to reusable KeyboardLayout.h utility + +1.6.0-bacon2 + ProjectView Render item + When in project view, it's now possible to initiate rendering + Interpolate values in tables and phrases + R + B on single-column selection will fill values from lowest to highest + + Contributions: + drbscl + Add 64 bit soundfont support (#211) + + Fixes: + Add 64 bit soundfont support (#211) + Skip randomly generated project name if a directory with that name already exists (#175) + Save Song As" saves to current as well as new project (#212) + 1.5.0-bacon3 Contributions: purelygrey @@ -15,9 +40,9 @@ * instrumentation by clsource simotek - Add color options for Border Play and Mute (#143) + Add color options for Border Play and Mute (#143) * Adds different color for the > play and _ mute indicators - Fix rg35xx mapping (#139) + Fix rg35xx mapping (#139) * rshoulder and lshoulder were the wrong way kompadre @@ -40,7 +65,7 @@ Nudge functionality (#130) * In Project view, select tempo and hold B+LEFT/RIGHT to nudge slower/faster * In Song view, pressing LT+LEFT/RIGHT will do the same - + Printable fx in InstrumentView (#155) * Uses ffmpeg to print reverb to currently selected sample * New sample with fx is created and assigned to the current instrument @@ -63,7 +88,7 @@ Set songview b jumping length to 16 rows in correspondence with LSDJ and M8 Scales no longer affect instrument numbers ([djdiskmachine/LittleGPTracker#172](https://github.com/djdiskmachine/LittleGPTracker/issues/172)) Bug in Variable:SetString [djdiskmachine/LittleGPTracker#169](https://github.com/djdiskmachine/LittleGPTracker/issues/169) - + Issue in deep clone (#135) Was possible to accidentally deep clone position into another position @@ -108,27 +133,27 @@ Fixes: RG35XX no longer segfaults on boot USB MIDI disabled due to missing OS dependencies - + 1.4.0 Adds: Automated build for bittboy, Win32, PSP, Miyoo, Deb32, RG35XX Shoutout to xquader for the initial RG35XX port (https://boosty.to/xquader) License changed from CC-BY-SA-NC to GPL-3 - * MVEL cmd + * MVEL cmd Set step velocity for MIDI instruments https://github.com/democloid/picoTracker/pull/163 - + Author: @maks@fluttercommunity.social Co-authored-by: djdiskmachine * Config option to set major beat color in phrase screen Author: koisignal - + * Deep clone Clones selected chain and the phrases within - + Author: koisignal Co-authored-by: djdiskmachine @@ -190,29 +215,29 @@ 1.3o-beta-1 Pingpong loop now stable - Courtesy of djdiskmachine + Courtesy of djdiskmachine Add ability to change font USAGE: ./mkfont.py FONT_BMP [OUTPUT_FILE] FONT_BMP must be a black/white (#000000, #FFFFFF) 128x64px bmp if OUTPUT_FILE is omitted, will print to stdout - Courtesy of subnixr + Courtesy of subnixr Adds Miyoo mini build Courtesy of Nine-H Changes filter attenuation to attenuation - Adjust volume post scream filter + Adjust volume post scream filter https://github.com/Mdashdotdashn/LittleGPTracker/issues/22 Missing wiki content dumped to repo - Courtesy of Peter Swimm + Courtesy of Peter Swimm 1.3o-alpha-5 Adds native SteamOS build Adds Bittboy/RS97/RG350 builds Merge of Gameblaba, CalebCDE and RafaVicos work - Shoutouts! + Shoutouts! 1.3o-alpha-4 Adds Save As menu item - save a copy of your project with a new name Courtesy of yoyz (https://github.com/yoyz), biggup! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..bd945b08 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +Thank you for considering contributing to this project! + +## Branch & Merge Strategy + +- **master** is the integration branch. It may contain unstable/experimental features. +- **Pull Requests** should target `master` +- **Releases** follow a release candidate workflow: + - Release candidates: `1.2.0-bacon0`, `1.2.0-bacon2`, etc. + - Stable releases: `v1.2.0` (after testing period) +- **Users should only use tagged releases**, build from master at your own peril, may contain half-done pork + +## Pull Request Requirements + +Before submitting a PR, ensure: + +- [ ] **Version bumped** in `sources/Application/Model/Project.h`: + - Bump BUILD_COUNT (`#define BUILD_COUNT "3-bacon3"`) for the most part + +- [ ] **Documentation updated**: + - Update `docs/wiki/What-is-LittlePiggyTracker.md` reflecting your changes + - Update CHANGELOG + +- [ ] **Clean commit history**: + - As changes are merged via squash commit, please provide a clear and descriptive commit message + +- [ ] **Testing**: + - Describe how you tested your changes in the PR description + - Include relevant test results + +## Merge Process + +Maintainer will review and merge approved PRs using **squash and merge**, combining your commits with a detailed merge message. + +## Questions? + +Open an issue or reach out to maintainers. diff --git a/README.md b/README.md index ce6d7ee7..3b3a3899 100644 --- a/README.md +++ b/README.md @@ -40,25 +40,26 @@ Recommended reading to get you started: ## Features per platform -| Platform | MIDI_Possible | MIDI_enabled | Soundfonts | Note | -|-------------|---------------|--------------|------------|--------------------------------------| -| PSP | NO | NO | YES | [See notes](projects/resources/PSP/INSTALL_HOW_TO.txt) | -| DEB | YES | YES | YES | | -| X64 | YES | YES | NO | | -| X86 | YES | YES | YES | | -| STEAM | YES | YES | NO | | -| MIYOO | NO | NO | YES | Port by [Nine-H](https://ninethehacker.xyz) | -| W32 | YES | YES | YES | Built in VS2008 with love | -| RASPI | YES | YES | YES | Versatile platform | -| CHIP | YES | YES | YES | [See notes](projects/resources/CHIP/INSTALL_HOW_TO.txt) | -| BITTBOY | MAYBE | NO | YES | | -| GARLIC | MAYBE | NO | YES | Port by [Simotek](http://simotek.net)| -| GARLICPLUS | MAYBE | NO | YES | Port by [Simotek](http://simotek.net)| -| RG35XXPLUS | MAYBE | NO | YES | Port by [Simotek](http://simotek.net)| -| MACOS | YES | YES | NO | Port by [clsource](https://genserver.social/clsource) | - - -* **Soundfont library is currently not ported for 64bit OS** +| Platform | MIDI_Possible | MIDI_enabled | Soundfonts | PrintFX | Note | +|-------------|---------------|--------------|------------|---------|--------------------------------------| +| PSP | NO | NO | YES | NO | [See notes](projects/resources/PSP/INSTALL_HOW_TO.txt) | +| DEB | YES | YES | YES | YES | | +| X64 | YES | YES | MAYBE | YES | | +| X86 | YES | YES | YES | YES | | +| STEAM | YES | YES | MAYBE | YES | | +| MIYOO | NO | NO | YES | NO | Port by [Nine-H](https://ninethehacker.xyz) | +| W32 | YES | YES | YES | YES | Built in VS2008 with love | +| RASPI | YES | YES | YES | YES | Versatile platform | +| CHIP | YES | YES | YES | YES | [See notes](projects/resources/CHIP/INSTALL_HOW_TO.txt) | +| BITTBOY | MAYBE | NO | YES | YES | | +| PORTMASTER | MAYBE | NO | YES | YES | | +| GARLIC | MAYBE | NO | YES | NO | Port by [Simotek](http://simotek.net)| +| GARLICPLUS | MAYBE | NO | YES | YES | Port by [Simotek](http://simotek.net)| +| RG35XXPLUS | MAYBE | NO | YES | NO | Port by [Simotek](http://simotek.net)| +| MACOS | YES | YES | MAYBE | NO | Port by [clsource](https://genserver.social/clsource) | + + +* **Soundfont library is now ported for 64bit OS** * **MIDI functionality __greatly__ depends on kernel support, please feature request your favourite OS maintainer =)** * **Install ffmpeg by following install instructions for your platform [here](https://www.ffmpeg.org/download.html)** -* **PrintFX requires full ffmpeg. If marked as TBA, it requires a redesign using [libav](https://trac.ffmpeg.org/wiki/Using%20libav*)** +* **PrintFX requires toolchain support of ffmpeg libs, for all targets marked maybe contact the maintainer of your consoles toolchain** diff --git a/docs/LittlePiggyTrackerConf.md b/docs/LittlePiggyTrackerConf.md index 6f6acfd1..cb6bd4aa 100644 --- a/docs/LittlePiggyTrackerConf.md +++ b/docs/LittlePiggyTrackerConf.md @@ -13,7 +13,6 @@ 06. [Key and Button Mapping](#key-and-button-mapping) 07. [Auto Repeat](#auto-repeat) 08. [Path](#path) -09. [Rendering](#rendering) 10. [Volume](#volume) 11. [Audio Configuration](#audio-configuration) 12. [MIDI Configuration](#midi-configuration) @@ -246,18 +245,6 @@ You can tweak two different path: ``` -## Rendering - -Additionally to playing the song, LittleGPTracker can be used to render the audio to file. To control file rendering, the variable `RENDER` can be set to either `FILE`,`FILESPLIT`,`FILERT`,`FILESPLITRT`. Note that there's a small issue with the speed when using `FILE`/`FILESPLIT` so the xxRT seem like the best choice at the moment -The xxRT options render in real time -The xSPLITx options render separate files for the channels (stems) - -```xml - - - -``` - ## Volume For \[**GP2X**/**Dingoo**\] only diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md index 13641f55..ae2b04a5 100644 --- a/docs/wiki/What-is-LittlePiggyTracker.md +++ b/docs/wiki/What-is-LittlePiggyTracker.md @@ -51,7 +51,24 @@ After that you can copy additional wavs to the lgptRoot/lgptProject/samples dire ## New project -When creating a new project, use the regen button to generate a random name. Generate a new name with Regen or edit it manually selecting characters with A and pressing up/down +When creating a new project, you have several options for naming: + +**Random Name Generation:** +- Select the "Random" button and press A to generate a random name + +**QWERTY Keyboard Entry:** +- Move to the name field and press A to enter QWERTY keyboard mode +- An on-screen keyboard will appear with these controls: + - **D-PAD/Arrows:** Navigate the keyboard + - **A:** Input the selected character + - **B:** Backspace (delete character) + - **L/R:** Move the text cursor left/right within your project name + - **START or OK key:** Exit keyboard mode and return to the dialog +- The keyboard includes: + - Numbers (0-9) + - Uppercase and lowercase letters (A-Z, a-z) + - Special characters (@ | - _ < > ? ,) + - Space bar, backspace, and OK (done) buttons on the bottom row ## Multiple Projects @@ -117,6 +134,7 @@ Note: CTRL Key mappings of RT and LT are inverted. Since the keyboard's Arrow Ke - B+LEFT/RIGHT: Next/Previous Channel in Chain/Phrase Screen. Navigation +/- 1 in Instrument/Table Screen. Switch between Song and Live Modes in Song Screen. - RT+ARROWS: Navigate between the Screens. - LT+UP/DOWN: Jump up/down to next populated row after a blank row (great for live mode entire row queuing!) +- RT+B: in chains or tables, a single-column selection will fill values from lowest to highest ## Selections @@ -136,6 +154,14 @@ And then: - LT+A: paste the clipboard content at current location +- RT+B: in chains or tables, a single-column selection will fill values from lowest to highest + +00 01 00 01 +01 -- => 01 02 +02 -- 02 03 +03 04 03 04 + + ## Playback Modes and Controls There are two modes for playback, Song and Live. The controls in each mode differ slightly. @@ -581,20 +607,11 @@ RTRG 0101: does not do anything because after looping one tick, you move forward # Rendering Some people exploit the analog gap between their device's headphone output and whatever they are recording with. Alternately, you can start piggy in rendering mode so it will output 16bit, 44100Hz .WAV files. -Please note that RENDER mode is not intended to be functional on the GP2X Builds. -The following values can set for RENDER in the config.xml: +The following values can set for RENDER in the project view -- Standard mode: audio is played; no render. -- FILE: File rendering: Full speed (no audio) rendering of the stereo mixdown. -- FILESPLIT: File split rendering: Full speed (no audio) rendering of each channel separately. -- FILERT: Real Time file rendering: Renders the mixdown to file WHILE playing audio. This allow to render live mode tweaks directly. -- FILESPLITRT: Real Time file split: same except all channels are rendered separately. - -Here is an example of the proper XML syntax: (See [The config.xml setup guide](../LittlePiggyTrackerConf.md)) - -```xml - -``` +- Off: audio is played; no render. +- Stereo: Real Time file rendering: Renders the mixdown to file WHILE playing audio. This allow to render live mode tweaks directly. +- Stems: Real Time file split: same except all channels are rendered separately. Remember, any of the config.xml parameters can be specified to lgpt on the command line in this fashion: diff --git a/docs/wiki/config_xml.md b/docs/wiki/config_xml.md index 3687abba..b910ed38 100644 --- a/docs/wiki/config_xml.md +++ b/docs/wiki/config_xml.md @@ -199,16 +199,6 @@ You can tweak two different path: ``` -## Rendering - -Additionally to playing the song, LittleGPTracker can be used to render the audio to file. To control file rendering, the variable RENDER can be set to either FILE,FILESPLIT,FILERT,FILESPLITRT. Note that there's a small issue with the speed when using FILE/FILESPLIT so the xxRT seem like the best choice at the moment - -```xml - - - -``` - ## Volume This setting is for GP2X and Dingoo only. It is used to set the volume of the hardware at startup. In decimal (base 10). diff --git a/docs/wiki/tips_and_tricks.md b/docs/wiki/tips_and_tricks.md index 470e2a87..15d84b16 100644 --- a/docs/wiki/tips_and_tricks.md +++ b/docs/wiki/tips_and_tricks.md @@ -1,3 +1,20 @@ +# Project Naming with QWERTY Keyboard + +Since version 1.6.0, you can use an on-screen QWERTY keyboard when creating new projects: + +**Quick Tips:** +- Press A on the project name to enter keyboard mode +- The keyboard cursor will jump to the character under your text cursor unless it's a space +- Use L/R shoulder buttons to move through your project name +- Erase with B and exit with Start + +**Keyboard Layout:** +The on-screen keyboard is organized like this: +- Numbers +- Uppercase (A-Q + extra characters) +- Lowercase (a-q + extra characters) +- Special [Space] [Erase] [Done] + # Delays and Echoes ## Simulating LSDj's D command diff --git a/docs/wiki/ubuntu_install.md b/docs/wiki/ubuntu_install.md index fef0874f..c78ac58c 100644 --- a/docs/wiki/ubuntu_install.md +++ b/docs/wiki/ubuntu_install.md @@ -58,4 +58,3 @@ Please remember: ##### Use Piggy as your Midi Sequencer -##### Script piggy to run in render mode from a simple xterm command diff --git a/projects/Makefile b/projects/Makefile index f735c9d7..7e0d3d5b 100644 --- a/projects/Makefile +++ b/projects/Makefile @@ -259,7 +259,8 @@ COMMONFILES := \ char.o n_assert.o fixed.o wildcard.o \ SyncMaster.o TablePlayback.o Player.o \ Table.o TableView.o\ - InstrumentBank.o WavFileWriter.o WavFile.o MidiInstrument.o Filters.o SampleVariable.o SampleInstrument.o SamplePool.o CommandList.o FxPrinter.o\ + InstrumentBank.o WavFileWriter.o WavFile.o MidiInstrument.o Filters.o \ + SampleVariable.o SampleInstrument.o SamplePool.o CommandList.o FxPrinter.o\ PersistencyService.o Persistent.o \ Observable.o SingletonRegistry.o \ Audio.o AudioMixer.o AudioOutDriver.o AudioDriver.o \ @@ -274,6 +275,12 @@ COMMONFILES := \ HexBuffers.o lz.o \ tinyxmlparser.o tinyxml.o tinyxmlerror.o tinystr.o Tiny2NosStub.o +ifneq ($(FFMPEG_ENABLED), 0) +ifneq ($(strip $(FFMPEG_LIBS)),) + COMMONFILES += LibavProcessor.o +endif +endif + #--------------------------------------------------------------------------------- # Linux #--------------------------------------------------------------------------------- @@ -512,7 +519,7 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) #--------------------------------------------------------------------------------- $(BUILD): - python3 ../sources/Resources/mkfont.py ../sources/Resources/$(FONT) ../sources/Resources/font.h +# python3 ../sources/Resources/mkfont.py ../sources/Resources/$(FONT) ../sources/Resources/font.h @[ -d $@ ] || mkdir -p $@ @make --no-print-directory -C $(BUILD) -f $(PWD)/Makefile diff --git a/projects/Makefile.BITTBOY b/projects/Makefile.BITTBOY index 2b505bcf..067ce5fc 100644 --- a/projects/Makefile.BITTBOY +++ b/projects/Makefile.BITTBOY @@ -1,4 +1,5 @@ -include $(PWD)/rules_base + STRIP = $(CROSS_COMPILE)strip DEFINES := \ @@ -8,6 +9,8 @@ DEFINES := \ -DHAVE_STDINT_H \ -D_NDEBUG \ -D__LINUX_ALSA__ \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API \ -D_NO_JACK_ DEVKIT=/opt/arm-buildroot-linux-musleabi_sdk-buildroot @@ -19,15 +22,17 @@ SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) SDL_BASE = $(DEVKIT)/arm-buildroot-linux-musleabi/sysroot/usr/bin/ +-include $(PWD)/rules_libav + TOOLPATH=$(DEVKIT)/usr/bin PREFIX := arm-linux- OPT_FLAGS = -O3 -Ofast -INCLUDES = -I$(PWD)/../sources -Iinclude $(SDL_CFLAGS) +INCLUDES = -I$(PWD)/../sources -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) $(SDL_CFLAGS) -Wall -DRS97 CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := $(SDL_LIBS) -lSDL -lSDL_mixer -lasound -lpthread -LIBDIRS := $(DEKVIT)/usr/lib +LIBS := $(SDL_LIBS) -lSDL -lSDL_mixer -lasound -lpthread $(FFMPEG_LIBS) +LIBDIRS := $(SYSROOT)/usr/lib OUTPUT = ../lgpt-bittboy EXTENSION:= elf diff --git a/projects/Makefile.GARLIC b/projects/Makefile.GARLIC index 657f6abe..0ac24c9e 100644 --- a/projects/Makefile.GARLIC +++ b/projects/Makefile.GARLIC @@ -19,6 +19,8 @@ SYSROOT := $(shell $(CROSS_COMPILE)gcc --print-sysroot) SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) +-include $(PWD)/rules_libav + # optimization OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard PREFIX := arm-linux-gnueabihf- @@ -26,8 +28,8 @@ INCLUDES:= -Iinclude $(SDL_CFLAGS) -I$(PWD)/../sources CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 LIBS := $(SDL_LIBS) -lpthread -LIBDIRS := $(DEKVIT)/usr/lib -LIBDIRS += $(DEKVIT)/usr/include +LIBDIRS := $(DEVKIT)/usr/lib +LIBDIRS += $(DEVKIT)/usr/include OUTPUT := ../lgpt-garlic EXTENSION:= elf diff --git a/projects/Makefile.GARLICPLUS b/projects/Makefile.GARLICPLUS index 72ce69c4..51b588d3 100644 --- a/projects/Makefile.GARLICPLUS +++ b/projects/Makefile.GARLICPLUS @@ -9,7 +9,10 @@ DEFINES := \ -DCPP_MEMORY \ -D_NDEBUG \ -DHAVE_STDINT_H \ - -D_NO_JACK_ + -D_NO_JACK_ \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API + # compiled using the https://github.com/shauninman/union-rg35xxplus-toolchain @@ -23,13 +26,24 @@ SYSROOT := $(shell $(CROSS_COMPILE)gcc --print-sysroot) SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + +FFMPEG_CFLAGS := $(shell $(DEVKIT)/usr/bin/pkg-config --cflags $(FFMPEG_INCL)) +FFMPEG_LIBS := $(shell $(DEVKIT)/usr/bin/pkg-config --libs $(FFMPEG_INCL)) + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) + # optimization OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard PREFIX := arm-linux-gnueabihf- -INCLUDES = -Iinclude $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES = -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := -lSDL -lpthread +LIBS := -lSDL -lpthread $(FFMPEG_LIBS) LIBDIRS := $(DEKVIT)/usr/lib LIBDIRS += $(DEKVIT)/usr/include OUTPUT = ../lgpt-garlicplus diff --git a/projects/Makefile.MIYOO b/projects/Makefile.MIYOO index e38c4117..9567f158 100644 --- a/projects/Makefile.MIYOO +++ b/projects/Makefile.MIYOO @@ -6,6 +6,8 @@ DEFINES := \ -DCPP_MEMORY \ -DHAVE_STDINT_H \ -D_NDEBUG \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API \ -D_NO_JACK_ DEVKIT = /opt/miyoomini-toolchain/ @@ -18,18 +20,28 @@ SYSROOT := $(shell $(CROSS_COMPILE)gcc --print-sysroot) SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) -INCLUDES = -Iinclude $(SDL_CFLAGS) -I$(PWD)/../sources -OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7ve+simd +LIBAV_PATH := arm-linux-gnueabihf/libc/usr/include +FFMPEG_CFLAGS := -I$(DEVKIT)/$(LIBAV_PATH)/libavformat \ + -I$(DEVKIT)/$(LIBAV_PATH)/libavfilter \ + -I$(DEVKIT)/$(LIBAV_PATH)/libavcodec \ + -I$(DEVKIT)/$(LIBAV_PATH)/libavutil \ + +FFMPEG_LIBS := -lavformat -lavfilter -lavcodec -lavutil +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) + +INCLUDES = -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources +OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7ve+simd TOOLPATH=$(DEVKIT)/usr/bin PREFIX := arm-linux-gnueabihf- CFLAGS := $(DEFINES) $(INCLUDES) $(SDL_CFLAGS) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := -lSDL -lSDL_mixer -lpthread $(SDL_LIBS) -LIBDIRS := $(DEKVIT)/usr/lib -LIBDIRS += $(DEKVIT)/usr/include +LIBS := -lSDL -lSDL_mixer -lpthread $(SDL_LIBS) $(FFMPEG_LIBS) +LIBDIRS := $(DEVKIT)/usr/lib +LIBDIRS += $(DEVKIT)/usr/include OUTPUT = ../lgpt-miyoo EXTENSION:= elf diff --git a/projects/Makefile.RG35XXPLUS b/projects/Makefile.RG35XXPLUS index dc04b211..e9f647f7 100644 --- a/projects/Makefile.RG35XXPLUS +++ b/projects/Makefile.RG35XXPLUS @@ -24,11 +24,23 @@ TOOLPATH=$(DEVKIT)/usr/bin SDL_CFLAGS := -I/$(SYSROOT)/include -D_REENTRANT SDL_LIBS := -lSDL2 +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + +LIBAV_PATH := aarch64-linux-gnu/include +FFMPEG_CFLAGS := -I$(DEVKIT)/$(LIBAV_PATH)/libavformat -I$(DEVKIT)/$(LIBAV_PATH)/libavfilter -I$(DEVKIT)/$(LIBAV_PATH)/libavcodec -I$(SYSROOT)/$(LIBAV_PATH)/libavutil +FFMPEG_LIBS := -lavformat -lavfilter -lavcodec -lavutil + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) + OPT_FLAGS = -O3 -mlittle-endian -mabi=lp64 -march=armv8-a+crypto+crc -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -dumpbase -INCLUDES = -Iinclude $(SDL_CFLAGS) -I$(DEVKIT)/$(TRIPLET)/include -I$(DEVKIT)/$(TRIPLET)/include/c++/11/ -I$(PWD)/../sources +INCLUDES = -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(DEVKIT)/$(TRIPLET)/include -I$(DEVKIT)/$(TRIPLET)/include/c++/11/ -I$(PWD)/../sources CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib/pulseaudio $(SDL_LIBS) -lpthread +LIBS := -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib/pulseaudio $(SDL_LIBS) $(FFMPEG_LIBS) -lpthread OUTPUT = ../lgpt-rg35xxplus EXTENSION:= elf diff --git a/projects/Makefile.X64 b/projects/Makefile.X64 index 6460f413..d9fa59a8 100644 --- a/projects/Makefile.X64 +++ b/projects/Makefile.X64 @@ -1,4 +1,5 @@ -include $(PWD)/rules_base +-include $(PWD)/rules_libav # config DEFINES := \ @@ -24,10 +25,10 @@ SDL_LIBS := $(shell pkg-config sdl2 --libs) OPT_FLAGS := -O3 #For debugging OPT_FLAGS := -g -INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(OPT_FLAGS) $(DEFINES) $(INCLUDES) -Wall CXXFLAGS := $(CFLAGS) -std=gnu++11 -LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) +LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) $(FFMPEG_LIBS) OUTPUT := ../lgpt EXTENSION := x64 diff --git a/projects/Makefile.X86 b/projects/Makefile.X86 index 4c8b3b86..8255db5e 100644 --- a/projects/Makefile.X86 +++ b/projects/Makefile.X86 @@ -11,21 +11,31 @@ DEFINES := \ -DRTMIDI \ -DFFMPEG_ENABLED +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + ALSA_CFLAGS := $(shell i686-linux-gnu-pkg-config alsa --cflags) ALSA_LIBS := $(shell i686-linux-gnu-pkg-config alsa --libs) JACK_CFLAGS := $(shell i686-linux-gnu-pkg-config jack --cflags) JACK_LIBS := $(shell i686-linux-gnu-pkg-config jack --libs) SDL_CFLAGS := $(shell i686-linux-gnu-pkg-config sdl2 --cflags) SDL_LIBS := $(shell i686-linux-gnu-pkg-config sdl2 --libs) +FFMPEG_CFLAGS := $(shell i686-linux-gnu-pkg-config $(FFMPEG_INCL) --cflags) +FFMPEG_LIBS := $(shell i686-linux-gnu-pkg-config $(FFMPEG_INCL) --libs) + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) # optimization OPT_FLAGS := -O3 -m32 #For debugging #OPT_FLAGS := -g -m32 -INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(OPT_FLAGS) $(DEFINES) $(INCLUDES) -Wall CXXFLAGS := $(CFLAGS) -LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) +LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) $(FFMPEG_LIBS) LDFLAGS := -m32 OUTPUT = ../lgpt EXTENSION := x86 diff --git a/projects/lgpt.vcproj b/projects/lgpt.vcproj index d50935b8..6439789c 100644 --- a/projects/lgpt.vcproj +++ b/projects/lgpt.vcproj @@ -42,8 +42,8 @@ Name="VCCLCompilerTool" AdditionalOptions="/MP" Optimization="0" - AdditionalIncludeDirectories="../sources/Externals;../sources/" - PreprocessorDefinitions="WIN32;_DEBUG;__WINDOWS_DS__;__WINDOWS_ASIO__;_CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;CPP_MEMORY;_LGPT_NO_SCREEN_CACHE_" + AdditionalIncludeDirectories="../sources/Externals;../sources/;C:\vcpkg\installed\x86-windows-static\include" + PreprocessorDefinitions="WIN32;_DEBUG;__WINDOWS_DS__;__WINDOWS_ASIO__;_CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;CPP_MEMORY;_LGPT_NO_SCREEN_CACHE_;FFMPEG_ENABLED;FFMPEG_LEGACY_API" MinimalRebuild="false" BasicRuntimeChecks="3" RuntimeLibrary="3" @@ -65,10 +65,10 @@ + + + + + diff --git a/projects/lgptest.dev b/projects/lgptest.dev index e877c121..e3e0ef5b 100644 --- a/projects/lgptest.dev +++ b/projects/lgptest.dev @@ -2937,7 +2937,6 @@ Priority=1000 OverrideBuildCmd=0 BuildCmd= - [Unit288] FileName=..\sources\Application\utils\RandomNames.h CompileCpp=1 @@ -2948,3 +2947,13 @@ Priority=1000 OverrideBuildCmd=0 BuildCmd= +[Unit289] +FileName=..\sources\Application\utils\KeyboardLayout.h +CompileCpp=1 +Folder=Application/Utils +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/projects/resources/GARLIC/LittleGPTracker.sh b/projects/resources/GARLIC/LittleGPTracker.sh index 51487a14..2fcd5602 100644 --- a/projects/resources/GARLIC/LittleGPTracker.sh +++ b/projects/resources/GARLIC/LittleGPTracker.sh @@ -2,5 +2,6 @@ progdir=$(dirname "$0")/lgpt cd $progdir HOME=$progdir -LD_PRELOAD=./j2k.so ./lgpt-rg35xx.elf &> log.txt +export LD_LIBRARY_PATH=$progdir/lib:$LD_LIBRARY_PATH +LD_PRELOAD=./j2k.so ./lgpt-garlic.elf &> lgpt.log sync diff --git a/projects/resources/GARLICPLUS/garlicos/LittleGPTracker.sh b/projects/resources/GARLICPLUS/LittleGPTracker-garlicos.sh similarity index 100% rename from projects/resources/GARLICPLUS/garlicos/LittleGPTracker.sh rename to projects/resources/GARLICPLUS/LittleGPTracker-garlicos.sh diff --git a/projects/resources/GARLICPLUS/muos/LittleGPTracker.sh b/projects/resources/GARLICPLUS/LittleGPTracker-muos.sh similarity index 100% rename from projects/resources/GARLICPLUS/muos/LittleGPTracker.sh rename to projects/resources/GARLICPLUS/LittleGPTracker-muos.sh diff --git a/projects/resources/packaging/lgpt_package.sh b/projects/resources/packaging/lgpt_package.sh index cedd7b1d..c6fd8a99 100755 --- a/projects/resources/packaging/lgpt_package.sh +++ b/projects/resources/packaging/lgpt_package.sh @@ -32,13 +32,17 @@ collect_resources() { #1PLATFORM #2lgpt.*-exe zip -9 $PACKAGE bin/* && rm -r bin/ fi cd ./resources/packaging - CONTENTS="../../../README.md ../../../CHANGELOG ../../../LICENSE samplelib/ lgpt_BETA/" + CONTENTS="../../../README.md ../../../CHANGELOG ../../../LICENSE" + CONTENTS+=" $(find . -name "samplelib" -type d)" + CONTENTS+=" $(find . -name "lgpt_*" -type d)" zip -9 -r ../../$PACKAGE $CONTENTS CONTENTS="../../../docs/wiki/What-is-LittlePiggyTracker.md" CONTENTS+=" ../../../docs/wiki/config_xml.md" CONTENTS+=" ../../../docs/wiki/tips_and_tricks.md" CONTENTS+=" ../$1/*.txt" - zip -9 ../../$PACKAGE -jq $CONTENTS && cd - + zip -9 ../../$PACKAGE -jq $CONTENTS + [ -d ../../lib ] && cd ../.. && zip -9 -r -y $PACKAGE lib && cd - + cd - } collect_resources PSP EBOOT.PBP diff --git a/projects/rules_libav b/projects/rules_libav new file mode 100644 index 00000000..b96ad7f7 --- /dev/null +++ b/projects/rules_libav @@ -0,0 +1,16 @@ +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + +$(info SYSROOT: $(SYSROOT)) +ifdef SYSROOT +FFMPEG_CFLAGS := $(shell $(DEVKIT)/bin/pkg-config --cflags $(FFMPEG_INCL)) +FFMPEG_LIBS := $(shell $(DEVKIT)/bin/pkg-config --libs $(FFMPEG_INCL)) +else +FFMPEG_CFLAGS := $(shell pkg-config --cflags $(FFMPEG_INCL)) +FFMPEG_LIBS := $(shell pkg-config --libs $(FFMPEG_INCL)) +endif + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) diff --git a/sources/Application/FX/FxPrinter.cpp b/sources/Application/FX/FxPrinter.cpp index 3c4a442a..cdf7426c 100644 --- a/sources/Application/FX/FxPrinter.cpp +++ b/sources/Application/FX/FxPrinter.cpp @@ -8,11 +8,6 @@ FxPrinter::FxPrinter(ViewData* viewData) InstrumentBank* bank = viewData_->project_->GetInstrumentBank(); instrument_ = static_cast(bank->GetInstrument(curInstr)); notificationResult_ = ""; - // Assume ffmpeg exists but swap for local ffmpig if it doesn't - ffmpeg_ = "ffmpeg"; - Path pigPath("bin:ffmpig"); - Path ffmpigPath(pigPath.GetPath().c_str()); - if(ffmpigPath.Exists()) ffmpeg_ = pigPath.GetPath(); } void FxPrinter::setParams() { @@ -21,10 +16,11 @@ void FxPrinter::setParams() { } void FxPrinter::setPaths() { - fi_ = std::string(instrument_->GetName()); + fi_ = std::string(instrument_->GetFullName()); + fiPath_ = samples_dir.GetPath() + '/' + fi_; - foWav_ = fi_.substr(0, fi_.find_last_of('.')) + "_.wav"; - fo_ = "\"" + samples_dir.GetPath() + '/' + foWav_ + "\""; + fo_ = fi_.substr(0, fi_.find_last_of('.')) + "_.wav"; + foPath_ = samples_dir.GetPath() + '/' + fo_; ir_ = impulse_dir.GetPath() + "/IR-s/"; ir_ += std::string(instrument_->FindVariable(SIP_PRINTFX)->GetString()); @@ -36,9 +32,9 @@ std::string FxPrinter::parseCommand() { float smplLength = static_cast(instrument_->GetSampleSize()) / 44100; std::ostringstream cm1, cm2, cm3, cm4, cm5; - cm1 << ffmpeg_ << " -y -i " - << "\"" << samples_dir.GetPath() << "/" << fi_ << "\"" - << " -i " << ir_ << " -filter_complex "; + // cm1 << ffmpeg_ << " -y -i " + // << "\"" << fi_ << "\"" + // << " -i " << ir_ << " -filter_complex "; // bug in ffmpeg version 4.4.2 // requires pad_dur to be over 0 or output will be infinitely long if (irPad_ > 0) { @@ -64,17 +60,22 @@ bool FxPrinter::Run() { setParams(); setPaths(); // Are we overwriting an already imported sample? - bool imported = SamplePool::GetInstance()->IsImported(foWav_); - std::string cmd = parseCommand(); - Trace::Log("Processed", cmd.c_str()); - if (system(cmd.c_str()) == 0) { - int newIndex = SamplePool::GetInstance()->Reassign(foWav_, imported); + bool imported = SamplePool::GetInstance()->IsImported(fo_); + parseCommand(); +#ifdef FFMPEG_ENABLED + if (encode(fiPath_.c_str(), ir_.c_str(), foPath_.c_str(), irWet_, irPad_) == 0) { + int newIndex = SamplePool::GetInstance()->Reassign(fo_, imported); instrument_->AssignSample(newIndex); - notificationResult_ = "OK!"; + notificationResult_ = "libav OK!"; return true; } else { Trace::Log("PRINTFX", "Failed"); notificationResult_ = "Failed, check lgpt.log"; return false; } +#else + Trace::Log("PRINTFX", "Failed"); + notificationResult_ = "Failed, check lgpt.log"; + return false; +#endif } diff --git a/sources/Application/FX/FxPrinter.h b/sources/Application/FX/FxPrinter.h index 61ad65ad..912fdae2 100644 --- a/sources/Application/FX/FxPrinter.h +++ b/sources/Application/FX/FxPrinter.h @@ -1,13 +1,16 @@ #ifndef FxPrinter_H #define FxPrinter_H -#include -#include -#include "Application/Views/ViewData.h" #include "Application/Instruments/InstrumentBank.h" #include "Application/Instruments/SampleInstrument.h" -#include "System/FileSystem/FileSystem.h" #include "Application/Instruments/SamplePool.h" +#include "Application/Views/ViewData.h" +#include "System/FileSystem/FileSystem.h" +#include +#include +#ifdef FFMPEG_ENABLED +#include "LibavProcessor.h" +#endif class FxPrinter { public: @@ -26,10 +29,10 @@ class FxPrinter { int irPad_; int irWet_; std::string fi_; + std::string fiPath_; std::string fo_; + std::string foPath_; std::string ir_; - std::string foWav_; - std::string ffmpeg_; char* notificationResult_; }; diff --git a/sources/Application/FX/LibavProcessor.c b/sources/Application/FX/LibavProcessor.c new file mode 100644 index 00000000..2c894cf5 --- /dev/null +++ b/sources/Application/FX/LibavProcessor.c @@ -0,0 +1,789 @@ +/** + * Apply effects to a file using libav + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static AVFormatContext *ifmt_ctx; +static AVFormatContext *ir_fmt_ctx; +static AVFormatContext *ofmt_ctx; +static AVFilterGraph *filter_graph; +static AVFilterContext *buffersrc_ctx; +static AVFilterContext *ir_buffersrc_ctx; +static AVFilterContext *buffersink_ctx; +static AVCodecContext *enc_ctx = NULL; + +static int open_input_file(const char *filename) +{ + const AVCodec *dec; + AVCodecContext *dec_ctx; + AVStream *stream; + int ret; + + if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open input file\n"); + return ret; + } + + if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find stream information\n"); + return ret; + } + + /* select the audio stream */ + ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find an audio stream in the input file\n"); + return ret; + } + + stream = ifmt_ctx->streams[ret]; + + /* create decoding context */ + dec_ctx = avcodec_alloc_context3(dec); + if (!dec_ctx) + return AVERROR(ENOMEM); + avcodec_parameters_to_context(dec_ctx, stream->codecpar); + + /* init the audio decoder */ + if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open audio decoder\n"); + return ret; + } + + stream->codecpar->codec_id = dec_ctx->codec_id; + stream->codecpar->sample_rate = dec_ctx->sample_rate; + stream->codecpar->format = dec_ctx->sample_fmt; +#ifdef FFMPEG_LEGACY_API + stream->codecpar->channels = dec_ctx->channels; + stream->codecpar->channel_layout = dec_ctx->channel_layout; +#else + av_channel_layout_copy(&stream->codecpar->ch_layout, &dec_ctx->ch_layout); +#endif + + avcodec_free_context(&dec_ctx); + + return 0; +} + +static int open_ir_file(const char *filename) +{ + const AVCodec *dec; + AVCodecContext *dec_ctx; + AVStream *stream; + int ret; + + if ((ret = avformat_open_input(&ir_fmt_ctx, filename, NULL, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open IR file\n"); + return ret; + } + + if ((ret = avformat_find_stream_info(ir_fmt_ctx, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find stream information in IR file\n"); + return ret; + } + + /* select the audio stream */ + ret = av_find_best_stream(ir_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find an audio stream in the IR file\n"); + return ret; + } + + stream = ir_fmt_ctx->streams[ret]; + + /* create decoding context */ + dec_ctx = avcodec_alloc_context3(dec); + if (!dec_ctx) + return AVERROR(ENOMEM); + avcodec_parameters_to_context(dec_ctx, stream->codecpar); + + /* init the audio decoder */ + if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open IR audio decoder\n"); + return ret; + } + + stream->codecpar->codec_id = dec_ctx->codec_id; + stream->codecpar->sample_rate = dec_ctx->sample_rate; + stream->codecpar->format = dec_ctx->sample_fmt; +#ifdef FFMPEG_LEGACY_API + stream->codecpar->channels = dec_ctx->channels; + stream->codecpar->channel_layout = dec_ctx->channel_layout; +#else + av_channel_layout_copy(&stream->codecpar->ch_layout, &dec_ctx->ch_layout); +#endif + + avcodec_free_context(&dec_ctx); + + return 0; +} + +static int open_output_file(const char *filename) +{ + AVStream *out_stream; + const AVCodec *encoder; + int ret; + + avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename); + if (!ofmt_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not create output context\n"); + return AVERROR_UNKNOWN; + } + out_stream = avformat_new_stream(ofmt_ctx, NULL); + if (!out_stream) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Failed allocating output stream\n"); + return AVERROR_UNKNOWN; + } + + /* find encoder - force PCM 16-bit little-endian as requested */ + encoder = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); + if (!encoder) { + av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n"); + return AVERROR_INVALIDDATA; + } + + enc_ctx = avcodec_alloc_context3(encoder); + if (!enc_ctx) { + av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n"); + return AVERROR(ENOMEM); + } + + /* Set output parameters to target format: 44.1kHz, 16-bit, stereo unless IR is mono */ + enc_ctx->sample_rate = 44100; + + /* Set channel layout based on IR format */ +#ifdef FFMPEG_LEGACY_API + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } + + if (ir_channels == 1) { + enc_ctx->channels = 1; + enc_ctx->channel_layout = AV_CH_LAYOUT_MONO; + } else { + enc_ctx->channels = 2; + enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO; + } +#else + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->ch_layout.nb_channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } + + if (ir_channels == 1) { + av_channel_layout_default(&enc_ctx->ch_layout, 1); /* Mono */ + } else { + av_channel_layout_default(&enc_ctx->ch_layout, 2); /* Stereo */ + } +#endif + + /* Use S16 format to match encoder */ + enc_ctx->sample_fmt = AV_SAMPLE_FMT_S16; + + /* Third parameter can be used to pass settings to encoder */ + ret = avcodec_open2(enc_ctx, encoder, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open video encoder for stream\n"); + return ret; + } + ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Failed to copy encoder parameters to output stream\n"); + return ret; + } + + out_stream->time_base = enc_ctx->time_base; + + av_dump_format(ofmt_ctx, 0, filename, 1); + + /* open the output file, if needed */ + if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { + ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not open output file '%s'", filename); + return ret; + } + } + + /* init muxer, write output file header */ + ret = avformat_write_header(ofmt_ctx, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error occurred when opening output file\n"); + return ret; + } + + return 0; +} + +static int init_filters(int ir_wet, int ir_pad) +{ + char args[512]; + char ir_args[512]; + int ret = 0; + const AVFilter *abuffer, *abuffersink; + AVFilterInOut *outputs = avfilter_inout_alloc(); + AVFilterInOut *inputs = avfilter_inout_alloc(); + AVRational time_base = ifmt_ctx->streams[0]->time_base; + AVRational ir_time_base = ir_fmt_ctx->streams[0]->time_base; + + /* Determine the target format - always 44.1kHz, stereo unless IR is mono */ + char target_layout[64]; + int target_sample_rate = 44100; + + /* Determine output channel layout based on IR format */ +#ifdef FFMPEG_LEGACY_API + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } +#else + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->ch_layout.nb_channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } +#endif + + if (ir_channels == 1) { + strcpy(target_layout, "mono"); /* If IR is mono, output mono for simplicity */ + } else { + strcpy(target_layout, "stereo"); /* Otherwise, output stereo */ + } + + filter_graph = avfilter_graph_alloc(); + if (!outputs || !inputs || !filter_graph) { + ret = AVERROR(ENOMEM); + goto end; + } + + /* buffer audio source: the decoded frames from the decoder will be inserted here. */ + abuffer = avfilter_get_by_name("abuffer"); + if (!abuffer) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not find the abuffer filter.\n"); + ret = AVERROR_FILTER_NOT_FOUND; + goto end; + } + + buffersrc_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "in"); + if (!buffersrc_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not allocate the abuffer instance.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + /* Set the filter options through the AVOptions API. */ + char ch_layout_str[64]; + // Use the actual channel layout from the input file +#ifdef FFMPEG_LEGACY_API + if (ifmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ch_layout_str, "mono"); + } else if (ifmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ch_layout_str, "stereo"); + } else { + snprintf(ch_layout_str, sizeof(ch_layout_str), "%dc", ifmt_ctx->streams[0]->codecpar->channels); + } +#else + if (ifmt_ctx->streams[0]->codecpar->ch_layout.nb_channels == 0) { + // If channel layout is unknown, default based on channel count + if (ifmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ch_layout_str, "mono"); + } else if (ifmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ch_layout_str, "stereo"); + } else { + snprintf(ch_layout_str, sizeof(ch_layout_str), "%dc", ifmt_ctx->streams[0]->codecpar->channels); + } + } else { + // Use the input stream's channel layout + av_channel_layout_describe(&ifmt_ctx->streams[0]->codecpar->ch_layout, + ch_layout_str, sizeof(ch_layout_str)); + } +#endif + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", + time_base.num, time_base.den, ifmt_ctx->streams[0]->codecpar->sample_rate, + av_get_sample_fmt_name(ifmt_ctx->streams[0]->codecpar->format), + ch_layout_str); + ret = avfilter_init_str(buffersrc_ctx, args); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not initialize the abuffer filter.\n"); + goto end; + } + + /* buffer audio source for IR: the decoded IR frames will be inserted here. */ + ir_buffersrc_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "ir_in"); + if (!ir_buffersrc_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not allocate the IR abuffer instance.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + char ir_ch_layout_str[64]; + // Use the actual channel layout from the IR file +#ifdef FFMPEG_LEGACY_API + if (ir_fmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ir_ch_layout_str, "mono"); + } else if (ir_fmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ir_ch_layout_str, "stereo"); + } else { + snprintf(ir_ch_layout_str, sizeof(ir_ch_layout_str), "%dc", ir_fmt_ctx->streams[0]->codecpar->channels); + } +#else + if (ir_fmt_ctx->streams[0]->codecpar->ch_layout.nb_channels == 0) { + // If channel layout is unknown, default based on channel count + if (ir_fmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ir_ch_layout_str, "mono"); + } else if (ir_fmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ir_ch_layout_str, "stereo"); + } else { + snprintf(ir_ch_layout_str, sizeof(ir_ch_layout_str), "%dc", ir_fmt_ctx->streams[0]->codecpar->channels); + } + } else { + // Use the IR stream's channel layout + av_channel_layout_describe(&ir_fmt_ctx->streams[0]->codecpar->ch_layout, + ir_ch_layout_str, sizeof(ir_ch_layout_str)); + } +#endif + snprintf(ir_args, sizeof(ir_args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", + ir_time_base.num, ir_time_base.den, ir_fmt_ctx->streams[0]->codecpar->sample_rate, + av_get_sample_fmt_name(ir_fmt_ctx->streams[0]->codecpar->format), + ir_ch_layout_str); + ret = avfilter_init_str(ir_buffersrc_ctx, ir_args); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not initialize the IR abuffer filter.\n"); + goto end; + } + + /* buffer audio sink: to terminate the filter chain. */ + abuffersink = avfilter_get_by_name("abuffersink"); + if (!abuffersink) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not find the abuffersink filter.\n"); + ret = AVERROR_FILTER_NOT_FOUND; + goto end; + } + + buffersink_ctx = avfilter_graph_alloc_filter(filter_graph, abuffersink, "out"); + if (!buffersink_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not allocate the abuffersink instance.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + /* Configure the buffersink to accept the expected format */ + static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, -1 }; + ret = av_opt_set_int_list(buffersink_ctx, "sample_fmts", out_sample_fmts, -1, AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot set output sample format\n"); + goto end; + } + + /* This filter takes no options. */ + ret = avfilter_init_str(buffersink_ctx, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not initialize the abuffersink instance.\n"); + goto end; + } + + /* + * Set the endpoints for the filter graph. The filter_graph will + * be linked to the graph described by filters_descr. + */ + + /* + * The buffer source output must be connected to the input pad of + * the first filter described by filters_descr; since the first + * filter input label is "in", with the pointer, we leave this + * pad open. + */ + outputs->name = av_strdup("in"); + outputs->filter_ctx = buffersrc_ctx; + outputs->pad_idx = 0; + outputs->next = avfilter_inout_alloc(); + + outputs->next->name = av_strdup("ir_in"); + outputs->next->filter_ctx = ir_buffersrc_ctx; + outputs->next->pad_idx = 0; + outputs->next->next = NULL; + + /* + * The buffer sink input must be connected to the output pad of + * the last filter described by filters_descr; since the last + * filter output label is "out", with the pointer, we leave this + * pad open. + */ + inputs->name = av_strdup("out"); + inputs->filter_ctx = buffersink_ctx; + inputs->pad_idx = 0; + inputs->next = NULL; + + /* Apply convolution reverb using afir filter with format normalization */ + char filters_descr[512]; + /* Scale ir_wet from 0-100 to 0-10 for afir filter */ + int afir_wet = (ir_wet * 10) / 100; + if (afir_wet > 10) afir_wet = 10; + if (afir_wet < 0) afir_wet = 0; + + snprintf(filters_descr, sizeof(filters_descr), + "[ir_in]aresample=%d,aformat=sample_fmts=fltp:channel_layouts=%s[" + "ir_norm];" + "[in]aresample=%d,aformat=sample_fmts=fltp:channel_layouts=%s," + "asplit[in_1][in_2];" + "[in_1][ir_norm]afir=dry=10:wet=%d[reverb];" + "[in_2][reverb]amix=inputs=2:weights=1 " + "1,volume=2.5,aformat=sample_fmts=s16:channel_layouts=%s[out]", + target_sample_rate, target_layout, target_sample_rate, + target_layout, afir_wet, target_layout); + + av_log(NULL, AV_LOG_INFO, "Filter graph: %s\n", filters_descr); + + if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr, + &inputs, &outputs, NULL)) < 0) + goto end; + + if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) + goto end; + +end: + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + + return ret; +} + +static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int *got_frame) { + int ret; + int got_frame_local; + AVPacket *enc_pkt; + + if (!got_frame) + got_frame = &got_frame_local; + + if (!enc_ctx) { + return AVERROR(EINVAL); + } + + enc_pkt = av_packet_alloc(); + if (!enc_pkt) { + return AVERROR(ENOMEM); + } + + /* encode filtered frame */ + ret = avcodec_send_frame(enc_ctx, filt_frame); + if (ret < 0) + goto end; + + while (ret >= 0) { + ret = avcodec_receive_packet(enc_ctx, enc_pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + ret = 0; /* These are not errors */ + break; + } else if (ret < 0) + goto end; + + /* prepare packet for muxing */ + enc_pkt->stream_index = stream_index; + av_packet_rescale_ts(enc_pkt, + enc_ctx->time_base, + ofmt_ctx->streams[stream_index]->time_base); + + /* mux encoded frame */ + ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); + if (ret < 0) + break; + } + +end: + av_packet_free(&enc_pkt); + return ret; +} + +static int filter_encode_write_frame(AVFrame *frame, AVFrame *ir_frame, unsigned int stream_index) +{ + int ret; + AVFrame *filt_frame; + + /* Only add frames if they are provided */ + if (frame) { + ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the input filtergraph\n"); + return ret; + } + } + + if (ir_frame) { + ret = av_buffersrc_add_frame_flags(ir_buffersrc_ctx, ir_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the IR filtergraph\n"); + return ret; + } + } + + /* Always try to pull filtered frames from the filtergraph */ + while (1) { + filt_frame = av_frame_alloc(); + if (!filt_frame) { + ret = AVERROR(ENOMEM); + break; + } + ret = av_buffersink_get_frame(buffersink_ctx, filt_frame); + if (ret < 0) { + /* if no more frames for output - returns AVERROR(EAGAIN) + * if flushed and no more frames for output - returns AVERROR_EOF + * rewrite retcode to 0 to show it as normal procedure completion + */ + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + ret = 0; + av_frame_free(&filt_frame); + break; + } + + printf("Got filtered frame: %d samples, %d channels, %d Hz\n", +#ifdef FFMPEG_LEGACY_API + filt_frame->nb_samples, filt_frame->channels, + filt_frame->sample_rate); +#else + filt_frame->nb_samples, filt_frame->ch_layout.nb_channels, + filt_frame->sample_rate); +#endif + + ret = encode_write_frame(filt_frame, stream_index, NULL); + av_frame_free(&filt_frame); + if (ret < 0) + break; + } + + return ret; +} + +int encode(const char *fi, const char *ir, const char *fo, int irWet, + int irPad) { + int ret; + AVPacket *packet = NULL, *ir_packet = NULL; + AVFrame *frame = NULL, *ir_frame = NULL; + AVCodecContext *dec_ctx = NULL, *ir_dec_ctx = NULL; + int ir_loaded = 0; + + if ((ret = open_input_file(fi)) < 0) + goto end; + if ((ret = open_ir_file(ir)) < 0) + goto end; + if ((ret = open_output_file(fo)) < 0) + goto end; + if ((ret = init_filters(irWet, irPad)) < 0) + goto end; + + packet = av_packet_alloc(); + ir_packet = av_packet_alloc(); + frame = av_frame_alloc(); + ir_frame = av_frame_alloc(); + if (!packet || !ir_packet || !frame || !ir_frame) { + fprintf(stderr, "Could not allocate frame or packet\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + /* Set up decoder contexts */ + const AVCodec *dec = + avcodec_find_decoder(ifmt_ctx->streams[0]->codecpar->codec_id); + const AVCodec *ir_dec = + avcodec_find_decoder(ir_fmt_ctx->streams[0]->codecpar->codec_id); + + if (!dec || !ir_dec) { + fprintf(stderr, "Could not find decoders (dec=%p, ir_dec=%p)\n", dec, ir_dec); + ret = AVERROR_DECODER_NOT_FOUND; + goto end; + } + + dec_ctx = avcodec_alloc_context3(dec); + ir_dec_ctx = avcodec_alloc_context3(ir_dec); + + if (!dec_ctx || !ir_dec_ctx) { + fprintf(stderr, "Could not allocate decoder contexts\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + avcodec_parameters_to_context(dec_ctx, ifmt_ctx->streams[0]->codecpar); + avcodec_parameters_to_context(ir_dec_ctx, ir_fmt_ctx->streams[0]->codecpar); + + ret = avcodec_open2(dec_ctx, dec, NULL); + if (ret < 0) { + fprintf(stderr, "Could not open decoder: %s\n", av_err2str(ret)); + goto end; + } + + ret = avcodec_open2(ir_dec_ctx, ir_dec, NULL); + if (ret < 0) { + fprintf(stderr, "Could not open IR decoder: %s\n", av_err2str(ret)); + goto end; + } + + /* Process both streams - load ALL IR data first, then process main audio */ + int ir_eof = 0, main_eof = 0; + + /* First load all IR data */ + while (!ir_eof && av_read_frame(ir_fmt_ctx, ir_packet) >= 0) { + if (ir_packet->stream_index == 0) { + ret = avcodec_send_packet(ir_dec_ctx, ir_packet); + while (ret >= 0) { + ret = avcodec_receive_frame(ir_dec_ctx, ir_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + fprintf(stderr, "Error while decoding IR\n"); + goto end; + } + + ret = av_buffersrc_add_frame_flags(ir_buffersrc_ctx, ir_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the IR filtergraph\n"); + goto end; + } + ir_loaded = 1; + } + } + av_packet_unref(ir_packet); + } + + /* Signal end of IR stream */ + ret = av_buffersrc_add_frame_flags(ir_buffersrc_ctx, NULL, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error closing IR filtergraph\n"); + goto end; + } + + if (!ir_loaded) { + fprintf(stderr, "No impulse response data loaded\n"); + goto end; + } + + /* Now process the main audio */ + while (!main_eof && av_read_frame(ifmt_ctx, packet) >= 0) { + if (packet->stream_index == 0) { + ret = avcodec_send_packet(dec_ctx, packet); + while (ret >= 0) { + ret = avcodec_receive_frame(dec_ctx, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + fprintf(stderr, "Error while decoding\n"); + goto end; + } + + ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the input filtergraph\n"); + goto end; + } + /* Pull filtered frames immediately */ + while (1) { + AVFrame *filt_frame = av_frame_alloc(); + ret = av_buffersink_get_frame(buffersink_ctx, filt_frame); + if (ret == AVERROR(EAGAIN)) { + av_frame_free(&filt_frame); + break; /* Need more input */ + } else if (ret == AVERROR_EOF) { + av_frame_free(&filt_frame); + break; /* No more frames */ + } else if (ret < 0) { + printf("Error getting frame from filter: %s\n", av_err2str(ret)); + av_frame_free(&filt_frame); + goto end; + } + + ret = encode_write_frame(filt_frame, 0, NULL); + av_frame_free(&filt_frame); + if (ret < 0) { + goto end; + } + } + } + } + av_packet_unref(packet); + } + + /* flush the filter graph by sending EOF to input sources */ + ret = av_buffersrc_add_frame_flags(buffersrc_ctx, NULL, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while closing the input filtergraph\n"); + goto end; + } + + /* Pull any remaining frames from the filter graph */ + ret = filter_encode_write_frame(NULL, NULL, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Flushing filter failed\n"); + goto end; + } + + /* flush the encoder */ + ret = avcodec_send_frame(enc_ctx, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error sending NULL frame to encoder\n"); + goto end; + } + + while (ret >= 0) { + AVPacket *enc_pkt = av_packet_alloc(); + ret = avcodec_receive_packet(enc_ctx, enc_pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + av_packet_free(&enc_pkt); + break; + } else if (ret < 0) { + av_packet_free(&enc_pkt); + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error during encoder flush\n"); + goto end; + } + + enc_pkt->stream_index = 0; + av_packet_rescale_ts(enc_pkt, enc_ctx->time_base, ofmt_ctx->streams[0]->time_base); + ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); + av_packet_free(&enc_pkt); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error writing final packet\n"); + goto end; + } + } + + av_write_trailer(ofmt_ctx); +end: + if (dec_ctx) { + avcodec_free_context(&dec_ctx); + } + if (ir_dec_ctx) { + avcodec_free_context(&ir_dec_ctx); + } + av_frame_free(&frame); + av_frame_free(&ir_frame); + av_packet_free(&packet); + av_packet_free(&ir_packet); + avfilter_graph_free(&filter_graph); + avformat_close_input(&ifmt_ctx); + avformat_close_input(&ir_fmt_ctx); + + if (enc_ctx) { + avcodec_free_context(&enc_ctx); + } + + if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { + avio_closep(&ofmt_ctx->pb); + } + avformat_free_context(ofmt_ctx); + + if (ret < 0 && ret != AVERROR_EOF) { + fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); + return(1); + } + return 0; +} diff --git a/sources/Application/FX/LibavProcessor.h b/sources/Application/FX/LibavProcessor.h new file mode 100644 index 00000000..10b24fd9 --- /dev/null +++ b/sources/Application/FX/LibavProcessor.h @@ -0,0 +1,14 @@ +#ifndef LIBAV_PROC_H +#define LIBAV_PROC_H + +#ifdef __cplusplus +extern "C" { +#endif + +int encode(const char *fi, const char *ir, const char *fo, int irWet, int irPad); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index c1b4f6b6..6d08e834 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -1410,6 +1410,17 @@ const char *SampleInstrument::GetName() { return src; } +/* + Get full name, don't shorten it for + display width limitation purposes +*/ +const char *SampleInstrument::GetFullName() { + Variable *v = FindVariable(SIP_SAMPLE); + const char *src = v->GetString(); + + return src; +} + void SampleInstrument::Purge() { IteratorPtr it(GetIterator()) ; for (it->Begin();!it->IsDone();it->Next()) { diff --git a/sources/Application/Instruments/SampleInstrument.h b/sources/Application/Instruments/SampleInstrument.h index ebf47e28..ea0e2166 100644 --- a/sources/Application/Instruments/SampleInstrument.h +++ b/sources/Application/Instruments/SampleInstrument.h @@ -92,16 +92,16 @@ class SampleInstrument: public I_Instrument,I_Observer { int GetLoopEnd(); virtual const char *GetName() ; // returns sample name until real // namer is implemented - - static void EnableDownsamplingLegacy(); + virtual const char *GetFullName(); + static void EnableDownsamplingLegacy(); -protected: - void updateInstrumentData(bool search) ; - void doTickUpdate(int channel) ; - void doKRateUpdate(int channel) ; - void updateFeedback(renderParams *rp) ; + protected: + void updateInstrumentData(bool search); + void doTickUpdate(int channel); + void doKRateUpdate(int channel); + void updateFeedback(renderParams *rp); -private: + private: SoundSource *source_ ; struct renderParams renderParams_[SONG_CHANNEL_COUNT] ; bool running_ ; diff --git a/sources/Application/Instruments/SoundFontManager.cpp b/sources/Application/Instruments/SoundFontManager.cpp index 0acec549..d56a4efc 100644 --- a/sources/Application/Instruments/SoundFontManager.cpp +++ b/sources/Application/Instruments/SoundFontManager.cpp @@ -2,10 +2,6 @@ #include "System/System/System.h" #include "System/FileSystem/FileSystem.h" -#ifdef _64BIT -#include -#endif - SoundFontManager::SoundFontManager() { } ; @@ -66,14 +62,11 @@ sfBankID SoundFontManager::LoadBank(const char *path) { current.dwEnd=(current.dwEnd-current.dwStart) ; current.dwStartloop=(current.dwStartloop-current.dwStart) ; current.dwEndloop=(current.dwEndloop-current.dwStart) ; -#ifdef _64BIT - current.dwStart=(intptr_t)buffer ; -#else - current.dwStart=(DWORD)buffer ; -#endif + // ADDR is pointer-sized, works on both 32-bit and 64-bit + current.dwStart = (ADDR)buffer; - sampleData_.push_back(buffer) ; - } + sampleData_.push_back(buffer); + } fin->Close() ; SAFE_DELETE(fin) ; diff --git a/sources/Application/Mixer/MixerService.cpp b/sources/Application/Mixer/MixerService.cpp index abd6c722..daba8f7e 100644 --- a/sources/Application/Mixer/MixerService.cpp +++ b/sources/Application/Mixer/MixerService.cpp @@ -1,52 +1,34 @@ #include "MixerService.h" +#include "Application/Audio/DummyAudioOut.h" +#include "Application/Model/Config.h" +#include "Application/Model/Mixer.h" +#include "Application/Model/Project.h" #include "Services/Audio/Audio.h" #include "Services/Audio/AudioDriver.h" #include "Services/Midi/MidiService.h" #include "System/Console/Trace.h" -#include "Application/Model/Config.h" -#include "Application/Audio/DummyAudioOut.h" -#include "Application/Model/Mixer.h" -MixerService::MixerService(): - out_(0), - sync_(0) -{ - mode_=MSM_AUDIO ; - const char *render=Config::GetInstance()->GetValue("RENDER") ; - if (render) { - if (!strcmp(render,"FILERT")) { - mode_=MSM_FILERT ; - } ; - if (!strcmp(render,"FILE")) { - mode_=MSM_FILE ; - } ; - if (!strcmp(render,"FILESPLIT")) { - mode_=MSM_FILESPLIT ; - } ; - if (!strcmp(render,"FILESPLITRT")) { - mode_=MSM_FILESPLITRT ; - } ; - } ; -} ; +MixerService::MixerService() : out_(0), sync_(0), isRendering_(false) { + mode_ = MSRM_PLAYBACK; +}; -MixerService::~MixerService() { -} ; +MixerService::~MixerService() {}; /* * initializes the mixer service, config changes depending if we're in sequencer or render mode */ bool MixerService::Init() { - // create the output depending on rendering mode - out_ = 0; + // create the output depending on rendering mode + out_ = 0; switch (mode_) { - case MSM_FILE: - case MSM_FILESPLIT: - out_ = new DummyAudioOut(); - break; - default: - Audio *audio = Audio::GetInstance(); - out_ = audio->GetFirst(); - break; + case MSRM_STEREO: + case MSRM_STEMS: + out_ = new DummyAudioOut(); + break; + default: + Audio *audio = Audio::GetInstance(); + out_ = audio->GetFirst(); + break; } for (int i=0;iInsert(master_); } - switch(mode_) { - case MSM_AUDIO: - break ; - case MSM_FILERT: - case MSM_FILE: - out_->SetFileRenderer("project:mixdown.wav"); - break; - case MSM_FILESPLITRT: - case MSM_FILESPLIT: - for (int i=0;iAddObserver(*MidiService::GetInstance()); + initRendering(mode_); + out_->AddObserver(*MidiService::GetInstance()); } sync_=SDL_CreateMutex(); @@ -90,6 +57,23 @@ bool MixerService::Init() { return (result); }; +void MixerService::initRendering(MixerServiceRenderMode mode) { + switch(mode) { + case MSRM_PLAYBACK: + break; + case MSRM_STEREO: + out_->SetFileRenderer("project:mixdown.wav"); + break; + case MSRM_STEMS: + for (int i = 0; i < SONG_CHANNEL_COUNT; i++) { + char buffer[1024]; + sprintf(buffer, "project:channel%d.wav", i); + bus_[i].SetFileRenderer(buffer); + } + break; + } +} + void MixerService::Close() { if (out_) { out_->RemoveObserver(*MidiService::GetInstance()); @@ -98,21 +82,13 @@ void MixerService::Close() { master_.Empty() ; switch(mode_) { - case MSM_FILE: - case MSM_FILESPLIT: - SAFE_DELETE(out_) ; - break; - default: - break ; - } - switch(mode_) { - case MSM_FILESPLITRT: - case MSM_FILESPLIT: - break; - default: - break ; - } - } + case MSRM_STEMS: + case MSRM_STEREO: + break; + default: + break; + } + } for (int i=0;iStart() ; - if (out_) { - out_->AddObserver(*this) ; - out_->Start() ; + MidiService::GetInstance()->Start(); + if (out_) { + out_->AddObserver(*this); + out_->Start(); } return true ; } ; @@ -181,20 +163,22 @@ int MixerService::GetPlayedBufferPercentage() { } void MixerService::toggleRendering(bool enable) { - switch(mode_) { - case MSM_AUDIO: - break ; - case MSM_FILERT: - case MSM_FILE: - out_->EnableRendering(enable) ; - break ; - case MSM_FILESPLITRT: - case MSM_FILESPLIT: - for (int i=0;iEnableRendering(enable); + break; + case MSRM_STEMS: + initRendering(MSRM_STEMS); + for (int i = 0; i < SONG_CHANNEL_COUNT; i++) { + bus_[i].EnableRendering(enable); + }; + break; + } } void MixerService::OnPlayerStart() { diff --git a/sources/Application/Mixer/MixerService.h b/sources/Application/Mixer/MixerService.h index 8a7f6a5f..55de2f4a 100644 --- a/sources/Application/Mixer/MixerService.h +++ b/sources/Application/Mixer/MixerService.h @@ -13,13 +13,11 @@ #include "Services/Audio/AudioOut.h" #include "MixBus.h" -enum MixerServiceMode { - MSM_AUDIO, - MSM_FILE, - MSM_FILESPLIT, - MSM_FILERT, - MSM_FILESPLITRT -} ; +enum MixerServiceRenderMode { + MSRM_PLAYBACK, + MSRM_STEREO, + MSRM_STEMS, +}; #define MAX_BUS_COUNT 10 @@ -51,6 +49,8 @@ class MixerService: void SetPregain(int); void SetSoftclip(int, int); void SetMasterVolume(int); + void SetRenderMode(int); + bool IsRendering(); int GetPlayedBufferPercentage() ; virtual void Execute(FourCC id,float value) ; @@ -63,11 +63,12 @@ class MixerService: protected: void toggleRendering(bool enable) ; private: - AudioOut *out_ ; - MixBus master_ ; - MixBus bus_[MAX_BUS_COUNT] ; - MixerServiceMode mode_ ; - SDL_mutex *sync_ ; - + void initRendering(MixerServiceRenderMode); + AudioOut *out_; + MixBus master_; + MixBus bus_[MAX_BUS_COUNT]; + MixerServiceRenderMode mode_; + SDL_mutex *sync_; + bool isRendering_; } ; #endif diff --git a/sources/Application/Model/Project.cpp b/sources/Application/Model/Project.cpp index 784a21fb..56bcead3 100644 --- a/sources/Application/Model/Project.cpp +++ b/sources/Application/Model/Project.cpp @@ -41,6 +41,9 @@ tempoNudge_(0) new Variable("scale", VAR_SCALE, scaleNames, scaleCount, 0); this->Insert(scale); scale->SetInt(0); + Variable *renderMode = + new Variable("renderMode", VAR_RENDER, renderModes, MAX_RENDER_MODE, 0); + this->Insert(renderMode); // Reload the midi device list @@ -110,6 +113,12 @@ int Project::GetPregain() { return v->GetInt(); } +int Project::GetRenderMode() { + Variable *v = FindVariable(VAR_RENDER); + NAssert(v); + return v->GetInt(); +} + void Project::NudgeTempo(int value) { if((GetTempo() + tempoNudge_) > 0) tempoNudge_ += value; diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h index f4bb4410..e9fe65ab 100644 --- a/sources/Application/Model/Project.h +++ b/sources/Application/Model/Project.h @@ -17,54 +17,55 @@ #define VAR_SOFTCLIP_GAIN MAKE_FOURCC('S', 'F', 'G', 'N') #define VAR_PREGAIN MAKE_FOURCC('P', 'R', 'G', 'N') #define VAR_SCALE MAKE_FOURCC('S', 'C', 'A', 'L') +#define VAR_RENDER MAKE_FOURCC('R', 'N', 'D', 'R') #define PROJECT_NUMBER "1" -#define PROJECT_RELEASE "5" -#define BUILD_COUNT "0-bacon3" +#define PROJECT_RELEASE "6" +#define BUILD_COUNT "0-bacon7" #define MAX_TAP 3 class Project: public Persistent,public VariableContainer,I_Observer { public: - Project() ; - ~Project() ; - void Purge() ; - void PurgeInstruments(bool removeFromDisk) ; + Project(); + ~Project(); + void Purge(); + void PurgeInstruments(bool removeFromDisk); - Song *song_ ; - - int GetMasterVolume() ; - bool Wrap() ; - void OnTempoTap(); - void NudgeTempo(int value) ; - int GetScale(); - int GetTempo() ; // Takes nudging into account - int GetTranspose() ; - int GetSoftclip(); - int GetSoftclipGain(); - int GetPregain(); + Song *song_; - void Trigger(); + int GetMasterVolume(); + bool Wrap(); + void OnTempoTap(); + void NudgeTempo(int value); + int GetScale(); + int GetTempo(); // Takes nudging into account + int GetTranspose(); + int GetSoftclip(); + int GetSoftclipGain(); + int GetPregain(); + int GetRenderMode(); + void Trigger(); - // I_Observer - virtual void Update(Observable &o,I_ObservableData *d); - - InstrumentBank* GetInstrumentBank() ; - virtual void SaveContent(TiXmlNode *node) ; - virtual void RestoreContent(TiXmlElement *element); + static const unsigned int MAX_RENDER_MODE = 3; + // I_Observer + virtual void Update(Observable &o, I_ObservableData *d); - void LoadFirstGen(const char *root) ; + InstrumentBank *GetInstrumentBank(); + virtual void SaveContent(TiXmlNode *node); + virtual void RestoreContent(TiXmlElement *element); + + void LoadFirstGen(const char *root); protected: - void buildMidiDeviceList() ; -private: - InstrumentBank *instrumentBank_ ; - char **midiDeviceList_ ; - int midiDeviceListSize_ ; - int tempoNudge_ ; - unsigned long lastTap_[MAX_TAP] ; - unsigned int tempoTapCount_ ; -} ; + void buildMidiDeviceList(); +private: + InstrumentBank *instrumentBank_; + char **midiDeviceList_; + int midiDeviceListSize_; + int tempoNudge_; + unsigned long lastTap_[MAX_TAP]; + unsigned int tempoTapCount_; +}; #endif - diff --git a/sources/Application/Model/ProjectDatas.h b/sources/Application/Model/ProjectDatas.h index e4eaff0f..0f70f880 100644 --- a/sources/Application/Model/ProjectDatas.h +++ b/sources/Application/Model/ProjectDatas.h @@ -1,2 +1,6 @@ -char *softclipStates[] = {"Bypass", "Subtle", "Medium", "Heavy", "Insane"}; -char *softclipGainStates[] = {"[unity]", "[boost]"}; +#ifndef _PROJECTDATAS_H_ +#define _PROJECTDATAS_H_ +static const char *softclipStates[] = {"Bypass", "Subtle", "Medium", "Heavy", "Insane"}; +static const char *softclipGainStates[] = {"[unity]", "[boost]"}; +static const char *renderModes[] = {"Off", "Stereo", "Stems"}; +#endif \ No newline at end of file diff --git a/sources/Application/Persistency/PersistencyService.cpp b/sources/Application/Persistency/PersistencyService.cpp index ebcaaed6..5b6ee1af 100644 --- a/sources/Application/Persistency/PersistencyService.cpp +++ b/sources/Application/Persistency/PersistencyService.cpp @@ -7,12 +7,12 @@ PersistencyService::PersistencyService():Service(MAKE_FOURCC('S','V','P','S')) { } ; -void PersistencyService::Save() { +void PersistencyService::Save(const char *name) { - Path filename("project:lgptsav.dat") ; + Path filename(name); - TiXmlDocument doc(filename.GetPath()) ; - TiXmlElement first("LITTLEGPTRACKER") ; + TiXmlDocument doc(filename.GetPath()); + TiXmlElement first("LITTLEGPTRACKER") ; TiXmlNode *node=doc.InsertEndChild(first) ; // Loop on all registered service @@ -25,7 +25,7 @@ void PersistencyService::Save() { } ; doc.SaveFile() ; -} ; +}; bool PersistencyService::Load() { diff --git a/sources/Application/Persistency/PersistencyService.h b/sources/Application/Persistency/PersistencyService.h index 27171df1..410f9cae 100644 --- a/sources/Application/Persistency/PersistencyService.h +++ b/sources/Application/Persistency/PersistencyService.h @@ -8,8 +8,8 @@ class PersistencyService: public Service,public T_Singleton { public: PersistencyService() ; - void Save() ; - bool Load() ; + void Save(const char *name = "project:lgptsav.dat"); + bool Load() ; } ; class PersistencyDocument: public TiXmlDocument { diff --git a/sources/Application/Utils/KeyboardLayout.h b/sources/Application/Utils/KeyboardLayout.h new file mode 100644 index 00000000..c260c1cc --- /dev/null +++ b/sources/Application/Utils/KeyboardLayout.h @@ -0,0 +1,100 @@ +#ifndef _KEYBOARD_LAYOUT_H_ +#define _KEYBOARD_LAYOUT_H_ + +#include + +// Keyboard layout configuration +#define SPACE_ROW 7 +#define KEYBOARD_ROWS (SPACE_ROW + 1) + +#define SPCE_START 0 +#define SPCE_END 3 +#define BACK_START 4 +#define BACK_END 6 +#define DONE_START 8 +#define DONE_END 10 + +static const char* keyboardLayout[] = { + "1234567890", + "QWERTYUIOP", + "ASDFGHJKL@", + "ZXCVBNM,?>", + "qwertyuiop", + "asdfghjkl|", + "zxcvbnm-_<", + "[_] <- OK" +}; + +// Helper functions for special row section detection +inline bool isInSpaceSection(int col) { return col < SPCE_END; } +inline bool isInBackSection(int col) { return col >= BACK_START && col < BACK_END; } +inline bool isInDoneSection(int col) { return col >= DONE_START; } + +// Get the character at a specific keyboard position +inline char getKeyAtPosition(int row, int col) { + if (row < 0 || row >= KEYBOARD_ROWS) return '\0'; + const char* rowStr = keyboardLayout[row]; + + // Handle special row with SPC, BACK, and DONE + if (row == SPACE_ROW) { + if (col >= 0 && col < SPCE_END) return ' '; // [_] (space) + if (col >= BACK_START && col < BACK_END) return '\b'; // <- (backspace) + if (col >= DONE_START && col < DONE_END) return '\r'; // OK (carriage return) + return '\0'; + } + + int len = strlen(rowStr); + if (col < 0 || col >= len) return '\0'; + return rowStr[col]; +} + +// Find a character's position in the keyboard layout +inline void findCharacterInKeyboard(char ch, int &outRow, int &outCol) { + if (ch == ' ') return; // Skip space character + + // Search for character in keyboard layout (excluding special row) + for (int row = 0; row < SPACE_ROW; row++) { + const char* rowStr = keyboardLayout[row]; + int len = strlen(rowStr); + for (int col = 0; col < len; col++) { + if (rowStr[col] == ch) { + outRow = row; + outCol = col; + return; + } + } + } + // Character not found, don't change position +} + +// Clamp keyboard column to valid range for current row +inline void clampKeyboardColumn(int row, int& col) { + if (row == SPACE_ROW) { + if (col < SPCE_END) col = SPCE_START; + else if (col <= BACK_END) col = BACK_START; + else col = DONE_START; + } else { + int maxCol = strlen(keyboardLayout[row]) - 1; + if (col > maxCol) col = 0; + } +} + +// Cycle keyboard column left (-1) or right (+1) within current row +inline void cycleKeyboardColumn(int row, int direction, int& col) { + if (row == SPACE_ROW) { + if (direction < 0) { // LEFT + if (isInSpaceSection(col)) col = DONE_START; + else if (isInBackSection(col)) col = SPCE_START; + else col = BACK_START; + } else { // RIGHT + if (isInBackSection(col)) col = DONE_START; + else if (isInDoneSection(col)) col = SPCE_START; + else col = BACK_START; + } + } else { + int maxCol = strlen(keyboardLayout[row]) - 1; + col = (col + direction + maxCol + 1) % (maxCol + 1); + } +} + +#endif diff --git a/sources/Application/Views/BaseClasses/View.cpp b/sources/Application/Views/BaseClasses/View.cpp index 625fa1b9..689ae8ef 100644 --- a/sources/Application/Views/BaseClasses/View.cpp +++ b/sources/Application/Views/BaseClasses/View.cpp @@ -243,18 +243,22 @@ void View::EnableNotification() { if ((SDL_GetTicks() - notificationTime_) <= NOTIFICATION_TIMEOUT) { SetColor(CD_NORMAL); GUITextProperties props; - DrawString(10, 2, displayNotification_, props); - } else { + int xOffset = 4; + DrawString(xOffset, notiDistY_, displayNotification_.c_str(), props); + } else { displayNotification_ = ""; } } /* - Set displayed notification and save the current time + Set displayed notification + Saves the current time + Optionally set display y offset if not in a project (default == 2) + Allows negative offsets, use with care! */ -void View::SetNotification(const char *notification) { - notificationTime_ = SDL_GetTicks(); - displayNotification_ = (char*) notification; - isDirty_ = true; +void View::SetNotification(const char *notification, int offset) { + notificationTime_ = SDL_GetTicks(); + displayNotification_ = notification; + notiDistY_ = offset; + isDirty_ = true; } - diff --git a/sources/Application/Views/BaseClasses/View.h b/sources/Application/Views/BaseClasses/View.h index b7fff153..3d0f787f 100644 --- a/sources/Application/Views/BaseClasses/View.h +++ b/sources/Application/Views/BaseClasses/View.h @@ -119,7 +119,7 @@ class View : public Observable { void DoModal(ModalView *view, ModalViewCallback cb = 0); void EnableNotification(); - void SetNotification(const char *notification); + void SetNotification(const char *notification, int offset = 2); protected: virtual void ProcessButtonMask(unsigned short mask, bool pressed) = 0; @@ -161,7 +161,8 @@ class View : public Observable { bool locked_; uint32_t notificationTime_; uint16_t NOTIFICATION_TIMEOUT; - char *displayNotification_; + std::string displayNotification_; + int notiDistY_; static bool initPrivate_; ModalView *modalView_; ModalViewCallback modalViewCallback_; diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp index 3be2afcc..48366734 100644 --- a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp +++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp @@ -1,163 +1,318 @@ #include "NewProjectDialog.h" +#include "Application/Utils/KeyboardLayout.h" #include "Application/Utils/RandomNames.h" -static char *buttonText[BUTTONS_LENGTH] = { - (char *)"Regen", - (char *)"Ok", - (char *)"Cancel" -} ; +static char *buttonText[BUTTONS_LENGTH] = {(char *)"Random", (char *)"Ok", + (char *)"Cancel"}; #define DIALOG_WIDTH 20 -NewProjectDialog::NewProjectDialog(View &view):ModalView(view) {} +NewProjectDialog::NewProjectDialog(View &view, Path currentPath) + : ModalView(view), currentPath_(currentPath) {} -NewProjectDialog::~NewProjectDialog() { +NewProjectDialog::~NewProjectDialog() {} + +// Move text cursor left (-1) or right (+1) and update keyboard position +void NewProjectDialog::moveCursor(int direction) { + int newPos = currentChar_ + direction; + if (newPos >= 0 && newPos < MAX_NAME_LENGTH) { + currentChar_ = newPos; + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, keyboardCol_); + } } void NewProjectDialog::DrawView() { - SetWindow(DIALOG_WIDTH,5) ; + SetWindow(DIALOG_WIDTH, keyboardMode_ ? 15 : 5); - GUITextProperties props ; + GUITextProperties props; - SetColor(CD_NORMAL) ; + SetColor(CD_NORMAL); // Draw string int x = (DIALOG_WIDTH - MAX_NAME_LENGTH) / 3; char buffer[2]; - buffer[1]=0 ; - for (int i=0;i 0) { + currentChar_--; + name_[currentChar_] = ' '; + } + } else if (ch == '\r') { + // END key: exit keyboard mode (same as START) + keyboardMode_ = false; + isDirty_ = true; + // EndModal(0); + return; + } else if (ch != '\0') { + name_[currentChar_] = ch; + lastChar_ = ch; + if (currentChar_ < MAX_NAME_LENGTH - 1) { + currentChar_++; + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } + } + isDirty_ = true; + return; + } else if (mask == EPBM_B) { + // Backspace: delete character and move cursor left + if (currentChar_ > 0) { + currentChar_--; + name_[currentChar_] = ' '; + isDirty_ = true; + } + return; + } else if (mask == EPBM_L) { + // Move cursor left + moveCursor(-1); + isDirty_ = true; + return; + } else if (mask == EPBM_R) { + // Move cursor right + moveCursor(1); + isDirty_ = true; + return; + } else if (mask == EPBM_UP) { + keyboardRow_ = (keyboardRow_ - 1 + KEYBOARD_ROWS) % KEYBOARD_ROWS; + clampKeyboardColumn(keyboardRow_, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_DOWN) { + keyboardRow_ = (keyboardRow_ + 1) % KEYBOARD_ROWS; + clampKeyboardColumn(keyboardRow_, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_LEFT) { + cycleKeyboardColumn(keyboardRow_, -1, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_RIGHT) { + cycleKeyboardColumn(keyboardRow_, 1, keyboardCol_); + isDirty_ = true; + return; + } else if (mask == EPBM_START) { + keyboardMode_ = false; + isDirty_ = true; + return; + } + return; + } else if (mask & EPBM_A) { + if (mask == EPBM_A) { + std::string randomName = ""; + switch (selected_) { + case 0: + // Toggle keyboard mode + keyboardMode_ = !keyboardMode_; + // When entering keyboard mode, jump to current character + if (keyboardMode_) { + findCharacterInKeyboard(name_[currentChar_], keyboardRow_, + keyboardCol_); + } + isDirty_ = true; + break; + case 1: + do { + randomName = getRandomName(); + std::fill(name_ + randomName.length(), + name_ + sizeof(name_) / sizeof(name_[0]), ' '); + strncpy(name_, randomName.c_str(), randomName.length()); + lastChar_ = currentChar_ = randomName.length() - 1; + } while (currentPath_.Descend(GetName()).Exists()); + isDirty_ = true; + break; + case 2: + if (currentPath_.Descend(GetName()).Exists()) { + std::string res("Name " + std::string(name_) + " busy"); + View::SetNotification(res.c_str(), -6); + } else { + EndModal(1); + } + break; + case 3: + EndModal(0); + break; + } + } + if (mask & EPBM_UP) { + name_[currentChar_]+=1; lastChar_=name_[currentChar_] ; isDirty_=true ; - } - if (mask&EPBM_DOWN) { + } + if (mask&EPBM_DOWN) { name_[currentChar_]-=1; lastChar_=name_[currentChar_] ; isDirty_=true ; } - } else { - - // R Modifier - - if (mask & EPBM_R) { - } else { - // No modifier - if (mask == EPBM_UP) { - selected_ = (selected_ == 0) ? 1 : 0; - isDirty_ = true; - } - if (mask == EPBM_DOWN) { - selected_ = (selected_ == 0) ? 1 : 0; - isDirty_ = true; - } - - if (mask == EPBM_LEFT) { - switch (selected_) { - case 0: - if (currentChar_ > 0) - currentChar_--; - break; - case 1: - case 2: - case 3: - if (selected_ > 0) - selected_--; - break; - } - isDirty_ = true; - } - if (mask == EPBM_RIGHT) { - switch (selected_) { - case 0: - if (currentChar_ < MAX_NAME_LENGTH - 1) - currentChar_++; - else - selected_++; - break; - case 1: - case 2: - case 3: - if (selected_ < BUTTONS_LENGTH) - selected_++; - break; - } - isDirty_ = true; - } - } - } + } else { + + // A modifier + if (mask & EPBM_A) { + if (mask == EPBM_A) { + std::string randomName = getRandomName(); + switch (selected_) { + case 0: + if (name_[currentChar_] == ' ') { + name_[currentChar_] = lastChar_; + } + isDirty_ = true; + break; + case 1: + std::fill(name_ + randomName.length(), + name_ + sizeof(name_) / sizeof(name_[0]), ' '); + strncpy(name_, randomName.c_str(), randomName.length()); + lastChar_ = currentChar_ = randomName.length() - 1; + isDirty_ = true; + break; + case 2: + EndModal(1); + break; + case 3: + EndModal(0); + break; + } + } + } else { + + // R Modifier + + if (mask & EPBM_R) { + } else { + // No modifier + if (mask == EPBM_UP) { + selected_ = (selected_ == 0) ? 1 : 0; + isDirty_ = true; + } + if (mask == EPBM_DOWN) { + selected_ = (selected_ == 0) ? 1 : 0; + isDirty_ = true; + } + + if (mask == EPBM_LEFT) { + switch (selected_) { + case 0: + if (currentChar_ > 0) + currentChar_--; + break; + case 1: + case 2: + case 3: + if (selected_ > 0) + selected_--; + break; + } + isDirty_ = true; + } + if (mask == EPBM_RIGHT) { + switch (selected_) { + case 0: + if (currentChar_ < MAX_NAME_LENGTH - 1) + currentChar_++; + else + selected_++; + break; + case 1: + case 2: + case 3: + if (selected_ < BUTTONS_LENGTH) + selected_++; + break; + } + isDirty_ = true; + } + } + } } }; - std::string NewProjectDialog::GetName() { for (int i = MAX_NAME_LENGTH; i >= 0; i--) { if (name_[i]==' ') { diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.h b/sources/Application/Views/ModalDialogs/NewProjectDialog.h index ba1aa9c7..917e2953 100644 --- a/sources/Application/Views/ModalDialogs/NewProjectDialog.h +++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.h @@ -1,6 +1,7 @@ #ifndef _NEW_PROJECT_DIALOG_H_ #define _NEW_PROJECT_DIALOG_H_ +#include "Application/Utils/KeyboardLayout.h" #include "Application/Views/BaseClasses/ModalView.h" #include @@ -9,20 +10,25 @@ class NewProjectDialog:public ModalView { public: - NewProjectDialog(View &view) ; - virtual ~NewProjectDialog() ; + NewProjectDialog(View &view, Path currentPath = ""); + virtual ~NewProjectDialog(); - virtual void DrawView() ; - virtual void OnPlayerUpdate(PlayerEventType ,unsigned int currentTick) ; - virtual void OnFocus() ; - virtual void ProcessButtonMask(unsigned short mask,bool pressed) ; + virtual void DrawView(); + virtual void OnPlayerUpdate(PlayerEventType, unsigned int currentTick); + virtual void OnFocus(); + virtual void ProcessButtonMask(unsigned short mask, bool pressed); - std::string GetName() ; -private: - int selected_ ; - int lastChar_ ; - char name_[MAX_NAME_LENGTH+1] ; - int currentChar_ ; -} ; + std::string GetName(); +private: + Path currentPath_; + int selected_; + int lastChar_; + char name_[MAX_NAME_LENGTH + 1]; + int currentChar_; + bool keyboardMode_; + int keyboardRow_; + int keyboardCol_ ; + void moveCursor(int direction); +}; #endif diff --git a/sources/Application/Views/ModalDialogs/SelectProjectDialog.cpp b/sources/Application/Views/ModalDialogs/SelectProjectDialog.cpp index 218050f4..7c0dc506 100644 --- a/sources/Application/Views/ModalDialogs/SelectProjectDialog.cpp +++ b/sources/Application/Views/ModalDialogs/SelectProjectDialog.cpp @@ -9,11 +9,7 @@ #define LIST_SIZE 20 #define LIST_WIDTH 32 -static char *buttonText[3]= { - "Load", - "New", - "Exit" -} ; +static char *buttonText[3] = {"Load", "New", "Exit"}; Path SelectProjectDialog::lastFolder_("root:") ; int SelectProjectDialog::lastProject_ = 0 ; @@ -25,12 +21,11 @@ static void NewProjectCallback(View &v,ModalView &dialog) { std::string selected=npd.GetName() ; SelectProjectDialog &spd=(SelectProjectDialog&)v ; Result result = spd.OnNewProject(selected) ; - if (result.Failed()) - { - Trace::Error(result.GetDescription().c_str()); + if (result.Failed()) { + Trace::Error(result.GetDescription().c_str()); + } } - } -} ; +}; SelectProjectDialog::SelectProjectDialog(View &view):ModalView(view),content_(true) { } @@ -45,16 +40,17 @@ void SelectProjectDialog::DrawView() { GUITextProperties props ; SetColor(CD_NORMAL) ; + View::EnableNotification(); -// Draw projects + // Draw projects - int x=1 ; - int y=1 ; + int x = 1; + int y = 1; - if (currentProject_=topIndex_+LIST_SIZE) { + if (currentProject_ < topIndex_) { + topIndex_ = currentProject_; + }; + if (currentProject_>=topIndex_+LIST_SIZE) { topIndex_=currentProject_-LIST_SIZE+1 ; } ; @@ -113,12 +109,11 @@ void SelectProjectDialog::DrawView() { x=offset*(i+1)-strlen(text)/2 ; props.invert_=(i==selected_)?true:false ; DrawString(x,y,text,props) ; - } - + } }; -void SelectProjectDialog::OnPlayerUpdate(PlayerEventType ,unsigned int currentTick) { -}; +void SelectProjectDialog::OnPlayerUpdate(PlayerEventType, + unsigned int currentTick) {}; void SelectProjectDialog::OnFocus() { @@ -129,29 +124,30 @@ void SelectProjectDialog::OnFocus() { void SelectProjectDialog::ProcessButtonMask(unsigned short mask,bool pressed) { if (!pressed) return ; - - if (mask&EPBM_B) { - if (mask&EPBM_UP) warpToNextProject(-LIST_SIZE) ; - if (mask&EPBM_DOWN) warpToNextProject(LIST_SIZE) ; - } else { - - // A modifier - if (mask&EPBM_A) { - switch(selected_) { + + if (mask&EPBM_B) { + if (mask & EPBM_UP) + warpToNextProject(-LIST_SIZE); + if (mask&EPBM_DOWN) warpToNextProject(LIST_SIZE) ; + } else { + + // A modifier + if (mask & EPBM_A) { + switch (selected_) { case 0: // load { - //locate folder user had selected when they hit a - int count=0 ; - Path *current=0 ; - - IteratorPtr it(content_.GetIterator()) ; - for(it->Begin();!it->IsDone();it->Next()) { - if (count==currentProject_) { - current=&it->CurrentItem() ; - break ; - } - count++ ; - } + // locate folder user had selected when they hit a + int count = 0; + Path *current = 0; + + IteratorPtr it(content_.GetIterator()); + for (it->Begin(); !it->IsDone(); it->Next()) { + if (count == currentProject_) { + current = &it->CurrentItem(); + break; + } + count++; + } //check if folder is a project, indicated by 'lgpt' being the first 4 characters of the folder name std::string name = current->GetName() ; @@ -178,20 +174,21 @@ void SelectProjectDialog::ProcessButtonMask(unsigned short mask,bool pressed) { } case 1: // new { - NewProjectDialog *npd=new NewProjectDialog(*this) ; - DoModal(npd,NewProjectCallback) ; + NewProjectDialog *npd = + new NewProjectDialog(*this, currentPath_); + DoModal(npd,NewProjectCallback) ; break ; - } - case 2: // Exit ; - EndModal(0) ; + } + case 2: // Exit ; + EndModal(0) ; break ; } - } else { + } else { - // R Modifier + // R Modifier - if (mask&EPBM_R) { - } else { + if (mask & EPBM_R) { + } else { // No modifier if (mask==EPBM_UP) warpToNextProject(-1) ; if (mask==EPBM_DOWN) warpToNextProject(1) ; @@ -204,16 +201,16 @@ void SelectProjectDialog::ProcessButtonMask(unsigned short mask,bool pressed) { selected_=(selected_+1)%3 ; isDirty_=true ; } - } - } - } + } + } + } }; void SelectProjectDialog::warpToNextProject(int amount) { - int offset=currentProject_-topIndex_ ; - int size=content_.Size() ; - currentProject_+=amount ; + int offset = currentProject_ - topIndex_; + int size = content_.Size(); + currentProject_+=amount ; if (currentProject_<0) currentProject_+=size ; if (currentProject_>=size) currentProject_-=size ; @@ -223,8 +220,7 @@ void SelectProjectDialog::warpToNextProject(int amount) { topIndex_=0 ; } ; } - isDirty_=true ; - + isDirty_ = true; } Path SelectProjectDialog::GetSelection() { @@ -233,8 +229,14 @@ Path SelectProjectDialog::GetSelection() { Result SelectProjectDialog::OnNewProject(std::string &name) { - Path path = currentPath_.Descend(name); - Trace::Log("TMP","creating project at %s",path.GetPath().c_str()); + Path path = currentPath_.Descend(name); + if (path.Exists()) { + Trace::Log("SelectProjectDialog:OnNewProj","path already exists %s", path.GetPath().c_str()); + std::string res("Name " + name + " busy"); + View::SetNotification(res.c_str(), 0); + return Result(res); + } + Trace::Log("TMP","creating project at %s",path.GetPath().c_str()); selection_ = path ; Result result = FileSystem::GetInstance()->MakeDir(path.GetPath().c_str()) ; RETURN_IF_FAILED(result, ("Failed to create project dir for '%s", path.GetPath().c_str())); @@ -289,7 +291,5 @@ void SelectProjectDialog::setCurrentFolder(Path &path) { //reset & redraw screen topIndex_=0 ; currentProject_=0 ; - isDirty_=true ; + isDirty_ = true; } - - diff --git a/sources/Application/Views/PhraseView.cpp b/sources/Application/Views/PhraseView.cpp index c95bfb46..f370306a 100644 --- a/sources/Application/Views/PhraseView.cpp +++ b/sources/Application/Views/PhraseView.cpp @@ -505,6 +505,72 @@ void PhraseView::extendSelection() { isDirty_ = true; } } + +/****************************************************** + interpolateSelection: + expands the lowest value of selection to the highest + ******************************************************/ +void PhraseView::interpolateSelection() { + if (!clipboard_.active_) { + return; + } + + GUIRect rect = getSelectionRect(); + // Only interpolate if we're in note (0) or param (3, 5) columns + int col = rect.Left(); + if (col != rect.Right() || (col != 0 && col != 3 && col != 5)) { + return; + } + + int startRow = rect.Top(); + int endRow = rect.Bottom(); + // Need at least 2 rows to interpolate + if (endRow - startRow < 1) { + return; + } + + // Select the appropriate data array based on column + if (col == 0) { + // Note column + uchar *noteData = phrase_->note_ + (16 * viewData_->currentPhrase_); + + uchar startNote = noteData[startRow]; + uchar endNote = noteData[endRow]; + + if (startNote == 0xFF || endNote == 0xFF) { + View::SetNotification("No note info"); + return; + } + + int numSteps = endRow - startRow; + int noteDiff = (int)endNote - (int)startNote; + + for (int step = 0; step <= numSteps; step++) { + int row = startRow + step; + int value = startNote + (2 * noteDiff * step + numSteps) / (2 * numSteps); + noteData[row] = (uchar)value; + } + } else { + // Parameter columns (3 or 5) + ushort *paramData = (col == 3) ? + phrase_->param1_ + (16 * viewData_->currentPhrase_) : + phrase_->param2_ + (16 * viewData_->currentPhrase_); + + ushort startParam = paramData[startRow]; + ushort endParam = paramData[endRow]; + + int numSteps = endRow - startRow; + int paramDiff = (int)endParam - (int)startParam; + + for (int step = 0; step <= numSteps; step++) { + int row = startRow + step; + int value = startParam + (2 * paramDiff * step + numSteps) / (2 * numSteps); + paramData[row] = (ushort)value; + } + } + isDirty_ = true; +} + /****************************************************** copySelection: copies data in the current selection to the @@ -992,6 +1058,8 @@ void PhraseView::processSelectionButtonMask(unsigned short mask) { if (mask & EPBM_B) { if (mask & EPBM_L) { extendSelection(); + } else if (mask & EPBM_R) { + interpolateSelection(); } else { copySelection(); } @@ -1210,7 +1278,7 @@ void PhraseView::DrawView() { DrawString(pos._x, pos._y, buffer, props); setTextProps(props, 2, j, true); pos._y++; - if (j == row_ && (col_ == 2 || col_ == 3)) { + if (j == row_ && col_ == 2) { printHelpLegend(command, props); } } @@ -1257,7 +1325,7 @@ void PhraseView::DrawView() { DrawString(pos._x, pos._y, buffer, props); setTextProps(props, 4, j, true); pos._y++; - if (j == row_ && (col_ == 4 || col_ == 5)) { + if (j == row_ && col_ == 4) { printHelpLegend(command, props); } } diff --git a/sources/Application/Views/PhraseView.h b/sources/Application/Views/PhraseView.h index 7693164d..1e266254 100644 --- a/sources/Application/Views/PhraseView.h +++ b/sources/Application/Views/PhraseView.h @@ -33,6 +33,7 @@ class PhraseView : public View { GUIRect getSelectionRect(); void fillClipboardData(); + void interpolateSelection(); void copySelection(); void cutSelection(); void pasteClipboard(); diff --git a/sources/Application/Views/ProjectView.cpp b/sources/Application/Views/ProjectView.cpp index 2afe9516..0674049a 100644 --- a/sources/Application/Views/ProjectView.cpp +++ b/sources/Application/Views/ProjectView.cpp @@ -1,4 +1,6 @@ #include "ProjectView.h" +#include "Application/Mixer/MixerService.h" +#include "Application/Model/ProjectDatas.h" #include "Application/Model/Scale.h" #include "Application/Persistency/PersistencyService.h" #include "Application/Views/ModalDialogs/MessageBox.h" @@ -38,8 +40,8 @@ static void SaveAsProjectCallback(View &v,ModalView &dialog) { Path path_dstprjdir = Path(str_dstprjdir); Path path_dstsmpdir = Path(str_dstsmpdir); - Path path_srclgptdatsav = path_srcprjdir.GetPath() + "lgptsav.dat"; - Path path_dstlgptdatsav = path_dstprjdir.GetPath() + "/lgptsav.dat"; + Path path_srclgptdatsav = path_srcprjdir.GetPath() + "lgptsav_tmp.dat"; + Path path_dstlgptdatsav = path_dstprjdir.GetPath() + "/lgptsav.dat"; if (path_dstprjdir.Exists()) { Trace::Log("ProjectView", "Dst Dir '%s' Exist == true", @@ -55,10 +57,13 @@ static void SaveAsProjectCallback(View &v,ModalView &dialog) { return; }; - FSS.Copy(path_srclgptdatsav,path_dstlgptdatsav); + if (FSS.Copy(path_srclgptdatsav, path_dstlgptdatsav) > -1) { + FSS.Delete(path_srclgptdatsav); + } - I_Dir *idir_srcsmpdir=FileSystem::GetInstance()->Open(path_srcsmpdir.GetPath().c_str()); - if (idir_srcsmpdir) { + I_Dir *idir_srcsmpdir = + FileSystem::GetInstance()->Open(path_srcsmpdir.GetPath().c_str()); + if (idir_srcsmpdir) { idir_srcsmpdir->GetContent("*"); idir_srcsmpdir->Sort(); IteratorPtrit(idir_srcsmpdir->GetIterator()); @@ -78,13 +83,15 @@ static void SaveAsProjectCallback(View &v,ModalView &dialog) { } static void LoadCallback(View &v,ModalView &dialog) { - if (dialog.GetReturnCode()==MBL_YES) { + MixerService::GetInstance()->SetRenderMode(0); + if (dialog.GetReturnCode()==MBL_YES) { ((ProjectView &)v).OnLoadProject() ; } } ; static void QuitCallback(View &v,ModalView &dialog) { - if (dialog.GetReturnCode()==MBL_YES) { + MixerService::GetInstance()->SetRenderMode(0); + if (dialog.GetReturnCode()==MBL_YES) { ((ProjectView &)v).OnQuit() ; } } ; @@ -95,8 +102,8 @@ static void PurgeCallback(View &v,ModalView &dialog) { ProjectView::ProjectView(GUIWindow &w,ViewData *data):FieldView(w,data) { - lastClock_=0 ; - lastTick_=0 ; + lastClock_ = 0; + lastTick_ = 0; project_=data->project_ ; @@ -180,6 +187,13 @@ ProjectView::ProjectView(GUIWindow &w,ViewData *data):FieldView(w,data) { MidiService::GetInstance()->Size(), 1, 1); T_SimpleList::Insert(field); + position._y += 2; + v = project_->FindVariable(VAR_RENDER); + NAssert(v); + field = new UIIntVarField(position, *v, "Render: %s", 0, + project_->MAX_RENDER_MODE - 1, 1, 2); + T_SimpleList::Insert(field); + position._y += 2; a1 = new UIActionField("Exit", ACTION_QUIT, position); a1->AddObserver(*this); @@ -192,23 +206,34 @@ ProjectView::~ProjectView() { void ProjectView::ProcessButtonMask(unsigned short mask,bool pressed) { - if (!pressed) return ; + if (!pressed) + return; - FieldView::ProcessButtonMask(mask) ; + FieldView::ProcessButtonMask(mask); - if (mask&EPBM_R) { - if (mask&EPBM_DOWN) { + if (mask & EPBM_R) { + if (mask&EPBM_DOWN) { ViewType vt=VT_SONG; ViewEvent ve(VET_SWITCH_VIEW,&vt) ; SetChanged(); - NotifyObservers(&ve) ; - } - } else { - if (mask&EPBM_START) { - Player *player=Player::GetInstance() ; + NotifyObservers(&ve); + } + } else { + if (mask&EPBM_START) { + Player *player = Player::GetInstance(); + + int renderMode = viewData_->renderMode_; + if (renderMode > 0 && !player->IsRunning()) { + viewData_->isRendering_ = true; + View::SetNotification("Rendering started!"); + } else if (viewData_->isRendering_ && player->IsRunning()) { + viewData_->isRendering_ = false; + View::SetNotification("Rendering done!"); + } + player->OnStartButton(PM_SONG,viewData_->songX_,false,viewData_->songX_) ; } - } ; + }; } ; void ProjectView::DrawView() { @@ -227,8 +252,22 @@ void ProjectView::DrawView() { SetColor(CD_NORMAL); DrawString(pos._x,pos._y,projectString,props) ; - FieldView::Redraw() ; - drawMap() ; + FieldView::Redraw(); + drawMap(); + + int currentMode = project_->GetRenderMode(); + if ((viewData_->renderMode_ != currentMode) && !MixerService::GetInstance()->IsRendering()) { + // Mode changed + if (currentMode > 0 && viewData_->renderMode_ == 0) { + View::SetNotification("Rendering on, press start"); + } else if (currentMode == 0 && viewData_->renderMode_ > 0) { + View::SetNotification("Rendering off"); + } + viewData_->renderMode_ = currentMode; + MixerService::GetInstance()->SetRenderMode(currentMode); + } + + View::EnableNotification(); } ; void ProjectView::Update(Observable &,I_ObservableData *data) { @@ -240,11 +279,11 @@ void ProjectView::Update(Observable &,I_ObservableData *data) { # ifdef _64BIT int fourcc=*((int*)data); #else - int fourcc=(unsigned int)data ; + int fourcc = (unsigned int)data; #endif - UIField *focus=GetFocus() ; - if (fourcc!=ACTION_TEMPO_CHANGED) { + UIField *focus = GetFocus(); + if (fourcc!= ACTION_TEMPO_CHANGED) { focus->ClearFocus() ; focus->Draw(w_) ; w_.Flush() ; @@ -264,13 +303,14 @@ void ProjectView::Update(Observable &,I_ObservableData *data) { break ; } case ACTION_SAVE: { + MixerService::GetInstance()->SetRenderMode(0); PersistencyService *service = PersistencyService::GetInstance(); service->Save(); break; } case ACTION_SAVE_AS: { PersistencyService *service = PersistencyService::GetInstance(); - service->Save(); + service->Save("project:lgptsav_tmp.dat"); NewProjectDialog *mb = new NewProjectDialog(*this); DoModal(mb, SaveAsProjectCallback); break; diff --git a/sources/Application/Views/ProjectView.h b/sources/Application/Views/ProjectView.h index 1f9474cf..352a28e4 100644 --- a/sources/Application/Views/ProjectView.h +++ b/sources/Application/Views/ProjectView.h @@ -27,10 +27,10 @@ class ProjectView: public FieldView,public I_Observer { protected: private: - Project *project_ ; -// Debug - unsigned long lastTick_ ; - unsigned long lastClock_ ; - UIField *tempoField_ ; + Project *project_; + // Debug + unsigned long lastTick_; + unsigned long lastClock_; + UIField *tempoField_; } ; #endif diff --git a/sources/Application/Views/SongView.cpp b/sources/Application/Views/SongView.cpp index e184195a..39c8a59d 100644 --- a/sources/Application/Views/SongView.cpp +++ b/sources/Application/Views/SongView.cpp @@ -1,5 +1,7 @@ #include "SongView.h" #include "Application/Commands/ApplicationCommandDispatcher.h" +#include "Application/Mixer/MixerService.h" +#include "Application/Model/ProjectDatas.h" #include "Application/Player/Player.h" #include "Application/Utils/char.h" #include "System/Console/Trace.h" @@ -485,6 +487,14 @@ void SongView::onStart() { from = r.Left(); to = r.Right(); } + int renderMode = viewData_->renderMode_; + if (renderMode > 0 && !player->IsRunning()) { + viewData_->isRendering_ = true; + View::SetNotification("Rendering started!"); + } else if (viewData_->isRendering_ && player->IsRunning()) { + viewData_->isRendering_ = false; + View::SetNotification("Rendering done!"); + } player->OnSongStartButton(from, to, false, false); }; @@ -511,6 +521,7 @@ void SongView::onStop() { from = r.Left(); to = r.Right(); } + player->OnSongStartButton(from, to, true, false); }; diff --git a/sources/Application/Views/TableView.cpp b/sources/Application/Views/TableView.cpp index f16355d8..1fe50cad 100644 --- a/sources/Application/Views/TableView.cpp +++ b/sources/Application/Views/TableView.cpp @@ -119,6 +119,57 @@ void TableView::extendSelection() { } } +/****************************************************** + interpolateSelection: + expands the lowest value of selection to the highest + ******************************************************/ +void TableView::interpolateSelection() { + if (!clipboard_.active_) { + return; + } + + GUIRect rect = getSelectionRect(); + + // Only interpolate if we're in param columns (1, 3, 5) + int col = rect.Left(); + if (col != rect.Right() || (col != 1 && col != 3 && col != 5)) { + return; + } + + int startRow = rect.Top(); + int endRow = rect.Bottom(); + + // Need at least 2 rows to interpolate + if (endRow - startRow < 1) { + return; + } + + Table &table = TableHolder::GetInstance()->GetTable(viewData_->currentTable_); + + ushort *paramData; + if (col == 1) { + paramData = table.param1_; + } else if (col == 3) { + paramData = table.param2_; + } else { + paramData = table.param3_; + } + + ushort startParam = paramData[startRow]; + ushort endParam = paramData[endRow]; + + int numSteps = endRow - startRow; + int paramDiff = (int)endParam - (int)startParam; + + for (int step = 0; step <= numSteps; step++) { + int row = startRow + step; + int value = startParam + (2 * paramDiff * step + numSteps) / (2 * numSteps); + paramData[row] = (ushort)value; + } + + isDirty_ = true; +} + void TableView::copySelection() { // Keep up with row,col of selection coz @@ -619,6 +670,8 @@ void TableView::processSelectionButtonMask(unsigned short mask) { if (mask & EPBM_B) { if (mask & EPBM_L) { extendSelection(); + } else if (mask & EPBM_R) { + interpolateSelection(); } else { copySelection(); } @@ -750,7 +803,7 @@ void TableView::DrawView() { DrawString(pos._x, pos._y, buffer, props); setTextProps(props, 0, j, true); pos._y++; - if (j == row_ && (col_ == 0 || col_ == 1)) { + if (j == row_ && col_ == 0) { printHelpLegend(command, props); } } @@ -788,7 +841,7 @@ void TableView::DrawView() { DrawString(pos._x, pos._y, buffer, props); setTextProps(props, 2, j, true); pos._y++; - if (j == row_ && (col_ == 2 || col_ == 3)) { + if (j == row_ && col_ == 2) { printHelpLegend(command, props); } } @@ -826,7 +879,7 @@ void TableView::DrawView() { DrawString(pos._x, pos._y, buffer, props); setTextProps(props, 4, j, true); pos._y++; - if (j == row_ && (col_ == 4 || col_ == 5)) { + if (j == row_ && col_ == 5) { printHelpLegend(command, props); } } diff --git a/sources/Application/Views/TableView.h b/sources/Application/Views/TableView.h index 38a68a53..db11ca99 100644 --- a/sources/Application/Views/TableView.h +++ b/sources/Application/Views/TableView.h @@ -21,6 +21,7 @@ class TableView : public View { void cutPosition(); void pasteLast(); + void interpolateSelection(); void copySelection(); void cutSelection(); void pasteClipboard(); diff --git a/sources/Application/Views/ViewData.cpp b/sources/Application/Views/ViewData.cpp index 11376496..f4dca9ff 100644 --- a/sources/Application/Views/ViewData.cpp +++ b/sources/Application/Views/ViewData.cpp @@ -17,6 +17,8 @@ ViewData::ViewData(Project *project) { currentGroove_=0 ; mixerCol_=0 ; mixerRow_=0 ; + renderMode_ = 0; + isRendering_ = false; } ; ViewData::~ViewData() { diff --git a/sources/Application/Views/ViewData.h b/sources/Application/Views/ViewData.h index 08da6f19..26be7b9d 100644 --- a/sources/Application/Views/ViewData.h +++ b/sources/Application/Views/ViewData.h @@ -74,6 +74,12 @@ class ViewData { int mixerCol_ ; // int mixerRow_ ; + + // Render Settings + + int renderMode_ ; // Current render mode (0=audio, 1=mixdown, 2=split) + bool isRendering_ ; // True when actively rendering (playing with mode > 0) + // Player Settings PlayMode playMode_ ; diff --git a/sources/Externals/Soundfont/DATATYPE.H b/sources/Externals/Soundfont/DATATYPE.H index ef5bcf23..06d6d6e8 100644 --- a/sources/Externals/Soundfont/DATATYPE.H +++ b/sources/Externals/Soundfont/DATATYPE.H @@ -36,13 +36,25 @@ #include +#if defined(_MSC_VER) && _MSC_VER < 1600 + /* Workaround for MSVC 2008 and earlier */ + typedef signed __int32 int32_t; + typedef unsigned __int32 uint32_t; +#ifdef _WIN64 + typedef unsigned __int64 uintptr_t; +#else + typedef unsigned __int32 uintptr_t; +#endif +#else +#include +#endif -/************ -* Defines -************/ + /************ + * Defines + ************/ -#ifndef __BYTE_INCOHERENT /* Big Endian (IE 680x0) */ -#define __BYTE_COHERENT /* Little Endian (IE 80x86) */ +#ifndef __BYTE_INCOHERENT /* Big Endian (IE 680x0) */ +#define __BYTE_COHERENT /* Little Endian (IE 80x86) */ #endif #ifdef _UNIX_STUB_ @@ -51,90 +63,92 @@ #endif #ifdef EMU_WINDOWS - #include +#include #else - #define _export +#define _export #endif -/* Expected data values */ -#define CHAR_MINVAL -127 -#define CHAR_MAXVAL 127 -#define BYTE_MAXVAL 255 -#define SHRT_MINVAL -32767 -#define SHRT_MAXVAL 32767 -#define LONG_MINVAL -2147483647L -#define LONG_MAXVAL 2147483647L -#define DWORD_MAXVAL 4294967295L + /* Expected data values */ +#define CHAR_MINVAL -127 +#define CHAR_MAXVAL 127 +#define BYTE_MAXVAL 255 +#define SHRT_MINVAL -32767 +#define SHRT_MAXVAL 32767 +#define LONG_MINVAL -2147483647L +#define LONG_MAXVAL 2147483647L +#define DWORD_MAXVAL 4294967295L #ifndef FALSE - #define FALSE 0 +#define FALSE 0 #endif #ifndef TRUE - #define TRUE 1 +#define TRUE 1 #endif #ifndef EMU_WINDOWS // windows.h defines these already -// #define LOBYTE(x) ((x) & 0x00FF) -// #define HIBYTE(x) (((x) & 0xFF00) >> 8) + // #define LOBYTE(x) ((x) & 0x00FF) + // #define HIBYTE(x) (((x) & 0xFF00) >> 8) #endif + /************* + * Typedefs + *************/ -/************* -* Typedefs -*************/ - -typedef char CHAR; /* 8 bit signed value */ -typedef short SHORT; /* 16 bit signed value was: INT */ + typedef char CHAR; /* 8 bit signed value */ + typedef short SHORT; /* 16 bit signed value was: INT */ #ifdef EMU_WINDOWS - /***************************************************************** - * These idiosyncratic pointer definitions for memory allocations - * which are greater than 64K and are Intel-centric compiling - * environment necessities, at least for Windows environments. - * Perhaps, however, Windows NT will eliminate the 'huge' keyword - * requirement altogether. - *****************************************************************/ - typedef BYTE huge* BYTEPTR; - typedef WORD huge* UINTPTR; - typedef DWORD huge* DWORDPTR; - typedef void huge* VOIDPTR; + /***************************************************************** + * These idiosyncratic pointer definitions for memory allocations + * which are greater than 64K and are Intel-centric compiling + * environment necessities, at least for Windows environments. + * Perhaps, however, Windows NT will eliminate the 'huge' keyword + * requirement altogether. + *****************************************************************/ + typedef BYTE huge *BYTEPTR; + typedef WORD huge *UINTPTR; + typedef DWORD huge *DWORDPTR; + typedef void huge *VOIDPTR; + /* Pointer-sized type for runtime memory addresses (32 or 64-bit) */ + typedef uintptr_t ADDR; #else - typedef unsigned char BYTE; /* 8 bit unsigned value */ + typedef unsigned char BYTE; /* 8 bit unsigned value */ #ifndef BOOL - typedef int BOOL; /* 16 bit signed value */ + typedef int BOOL; /* 16 bit signed value */ #endif - typedef unsigned short WORD; /* 16 bit signed value */ - typedef signed long LONG; /* 32 bit signed value */ - typedef unsigned long DWORD; /* 32 bit unsigned value */ - typedef float FLOAT; /* 32 bit floating point value */ - typedef double DOUBLE; /* 64 bit floating point value */ - typedef long double LDOUBLE; /* 80 bit floating point value */ - - typedef BYTE* BYTEPTR; - typedef WORD* UINTPTR; - typedef DWORD* DWORDPTR; - typedef void* VOIDPTR; + typedef unsigned short WORD; /* 16 bit unsigned value */ + typedef int32_t LONG; /* 32 bit signed value (fixed-size) */ + typedef uint32_t DWORD; /* 32 bit unsigned value (fixed-size) */ + typedef float FLOAT; /* 32 bit floating point value */ + typedef double DOUBLE; /* 64 bit floating point value */ + typedef long double LDOUBLE; /* 80 bit floating point value */ + + /* Pointer-sized type for runtime memory addresses (32 or 64-bit) */ + typedef uintptr_t ADDR; + + typedef BYTE *BYTEPTR; + typedef WORD *UINTPTR; + typedef DWORD *DWORDPTR; + typedef void *VOIDPTR; #endif /* EMU_WINDOWS */ -/******************************************************************** -* This 16 bit unsigned value is used for routines which return -* standard E-mu error codes (see emuerrs.h) -********************************************************************/ -typedef unsigned short EMUSTAT; + /******************************************************************** + * This 16 bit unsigned value is used for routines which return + * standard E-mu error codes (see emuerrs.h) + ********************************************************************/ + typedef unsigned short EMUSTAT; #ifdef __BYTE_COHERENT -/******************************************************************** -* Convenient union datatypes to use for byte swapping or extracting -* bytes and/or shorts within a short or long value without using -* bit shifting. -********************************************************************/ -typedef struct twoBytesTag -{ - BYTE by0; - BYTE by1; -} twoBytes; - + /******************************************************************** + * Convenient union datatypes to use for byte swapping or extracting + * bytes and/or shorts within a short or long value without using + * bit shifting. + ********************************************************************/ + typedef struct twoBytesTag { + BYTE by0; + BYTE by1; + } twoBytes; typedef struct fourBytesTag { @@ -153,12 +167,10 @@ typedef struct twoWordsTag #elif defined(__BYTE_INCOHERENT) -typedef struct twoBytesTag -{ - BYTE by1; - BYTE by0; -} twoBytes; - + typedef struct twoBytesTag { + BYTE by1; + BYTE by0; + } twoBytes; typedef struct fourBytesTag { diff --git a/sources/Externals/Soundfont/HYDRA.H b/sources/Externals/Soundfont/HYDRA.H index 23d4672a..3f686570 100644 --- a/sources/Externals/Soundfont/HYDRA.H +++ b/sources/Externals/Soundfont/HYDRA.H @@ -206,10 +206,10 @@ typedef struct sfSampleHdrTag { CHAR achSampleName[SAMPLENAMESIZE]; - DWORD dwStart; // Sample addresses - DWORD dwEnd; - DWORD dwStartloop; - DWORD dwEndloop; + ADDR dwStart; // Sample addresses + ADDR dwEnd; + ADDR dwStartloop; + ADDR dwEndloop; DWORD dwSampleRate; // In Hz, IE 44100, 22050, etc BYTE byOriginalKey; // MIDI Key, 0 to 127 CHAR chFineCorrection; // Tuning correction in cents diff --git a/sources/Externals/Soundfont/RIFF.CPP b/sources/Externals/Soundfont/RIFF.CPP index cc092bd5..494c087e 100644 --- a/sources/Externals/Soundfont/RIFF.CPP +++ b/sources/Externals/Soundfont/RIFF.CPP @@ -235,10 +235,9 @@ WORD RIFFClass::OpenRIFF(CHAR* pName) { byWhereIsRIFFData = RIFF_ONDISK; - if ((RIFFOpen((LONG)pName) != SUCCESS ) || (pFile == NULL)) - { - SetError(errno); // Set inside RIFFOpen - return (uiErrorNdx = RIFF_OPENFILEERROR); + if ((RIFFOpen((ADDR)pName) != SUCCESS) || (pFile == NULL)) { + SetError(errno); // Set inside RIFFOpen + return (uiErrorNdx = RIFF_OPENFILEERROR); } return (InitRIFF()); @@ -253,10 +252,10 @@ WORD RIFFClass::OpenRIFF(CHAR* pName) WORD RIFFClass::OpenRIFF(FSSpec* pSpecifier) { byWhereIsRIFFData = RIFF_ONMACDISK; - - if (RIFFOpen((LONG)pSpecifier) != SUCCESS) - return (RIFF_OPENFILEERROR); - + + if (RIFFOpen((ADDR)pSpecifier) != SUCCESS) + return (RIFF_OPENFILEERROR); + return(InitRIFF()); } #endif // USE_MACINTOSH @@ -443,28 +442,26 @@ DWORD RIFFClass::GetCkFormID(void) { return (dwLastFormID); } // is a pointer to the File System Specifier (FSSpec) // data structure. //*************************************************** -SHORT RIFFClass::RIFFOpen(LONG lPointer) -{ - //*************************************************** - // Keep in mind that whenever a system call is made and - // _no_ error takes place, errno is _not_ set to 0. - // Obviously, this means that any previous system call - // will set errno upon an error, one which is innocuous, - // but will cause fopen to _appear_ as though it failed. - // For example, open a temporary file which might not - // exist: - // - // fopen("tempfile", "wb"); - // - // If it doesn't exist, errno become 2 (in DOS, at - // least). Now call the fopen below for a preexisting - // RIFF file. Guess what! If you didn't reset errno - // to 0, your return value will be 2, an apparent error. - //*************************************************** - errno = 0; - - switch (byWhereIsRIFFData) - { +SHORT RIFFClass::RIFFOpen(ADDR lPointer) { + //*************************************************** + // Keep in mind that whenever a system call is made and + // _no_ error takes place, errno is _not_ set to 0. + // Obviously, this means that any previous system call + // will set errno upon an error, one which is innocuous, + // but will cause fopen to _appear_ as though it failed. + // For example, open a temporary file which might not + // exist: + // + // fopen("tempfile", "wb"); + // + // If it doesn't exist, errno become 2 (in DOS, at + // least). Now call the fopen below for a preexisting + // RIFF file. Guess what! If you didn't reset errno + // to 0, your return value will be 2, an apparent error. + //*************************************************** + errno = 0; + + switch (byWhereIsRIFFData) { case RIFF_ONDISK: pFile = fopen((CHAR *)lPointer, "rb"); return (errno); @@ -476,7 +473,7 @@ SHORT RIFFClass::RIFFOpen(LONG lPointer) default: return (1); - } + } } SHORT RIFFClass::RIFFClose() diff --git a/sources/Externals/Soundfont/RIFF.H b/sources/Externals/Soundfont/RIFF.H index ff12655d..0763b5ec 100644 --- a/sources/Externals/Soundfont/RIFF.H +++ b/sources/Externals/Soundfont/RIFF.H @@ -191,7 +191,7 @@ class RIFFClass DWORD RIFFTell(void); DWORD RIFFTellAbs(void); - SHORT RIFFOpen(LONG); + SHORT RIFFOpen(ADDR); SHORT RIFFClose(void); diff --git a/sources/Externals/Soundfont/SFDATA.H b/sources/Externals/Soundfont/SFDATA.H index 1c4ff1c8..9540f8e3 100644 --- a/sources/Externals/Soundfont/SFDATA.H +++ b/sources/Externals/Soundfont/SFDATA.H @@ -53,10 +53,10 @@ typedef enum sfSampleFlagsTag typedef struct sfDataTag { //// Oscillator //// - DWORD dwStart; //// sample start address - DWORD dwEnd; - DWORD dwStartloop; //// loop start address - DWORD dwEndloop; //// loop end address + ADDR dwStart; //// sample start address (pointer-sized for 64-bit) + ADDR dwEnd; + ADDR dwStartloop; //// loop start address (pointer-sized for 64-bit) + ADDR dwEndloop; //// loop end address (pointer-sized for 64-bit) DWORD dwSampleRate; SHORT shOrigKeyAndCorr; SHORT shSampleModes; diff --git a/sources/Externals/Soundfont/SFNAV.CPP b/sources/Externals/Soundfont/SFNAV.CPP index d16cc892..af023797 100644 --- a/sources/Externals/Soundfont/SFNAV.CPP +++ b/sources/Externals/Soundfont/SFNAV.CPP @@ -468,8 +468,8 @@ void SoundFontNavigator::Navigate(WORD uiSFID, WORD uiKey, WORD uiVel) // This step necessary for Big Endian systems if (uiInstGenOper <= endloopAddrsOffset) - *(LONG*)((BYTE*)&(psfVectorCurrOsc->dwStart) + - soundFontLookup[uiInstGenOper]) = (LONG)iInstGenAmt; + *(ADDR *)((BYTE *)&(psfVectorCurrOsc->dwStart) + + soundFontLookup[uiInstGenOper]) = (ADDR)iInstGenAmt; else *(SHORT*)((BYTE*)&(psfVectorCurrOsc->dwStart) + soundFontLookup[uiInstGenOper]) = iInstGenAmt; @@ -520,8 +520,8 @@ void SoundFontNavigator::Navigate(WORD uiSFID, WORD uiKey, WORD uiVel) // Necessary for 'byte-incoherent' systems if (uiGenOper <= endloopAddrsOffset) - *(LONG*)((BYTE*)&sfCurrPreset.dwStart + - soundFontLookup[uiGenOper]) = (LONG)iGenAmt; + *(ADDR *)((BYTE *)&sfCurrPreset.dwStart + + soundFontLookup[uiGenOper]) = (ADDR)iGenAmt; else *(SHORT*)((BYTE*)&sfCurrPreset.dwStart + soundFontLookup[uiGenOper]) = iGenAmt; @@ -815,8 +815,13 @@ SoundFontNavigator::AddSoundFonts(sfData * sfSoundFontReturned, { uiOffset = (WORD) soundFontLookup[wCount]; - *(SHORT*)((BYTE*)&(sfSoundFontReturned->dwStart) + uiOffset) += - *(SHORT*)((BYTE*)&(sfSFPresetAdd->dwStart) + uiOffset); + if (wCount <= endloopAddrsOffset) { + *(ADDR *)((BYTE *)&(sfSoundFontReturned->dwStart) + uiOffset) += + *(ADDR *)((BYTE *)&(sfSFPresetAdd->dwStart) + uiOffset); + } else { + *(SHORT *)((BYTE *)&(sfSoundFontReturned->dwStart) + uiOffset) += + *(SHORT *)((BYTE *)&(sfSFPresetAdd->dwStart) + uiOffset); + } } } diff --git a/sources/Externals/Soundfont/SFREADER.CPP b/sources/Externals/Soundfont/SFREADER.CPP index 5004ae16..94e4d368 100644 --- a/sources/Externals/Soundfont/SFREADER.CPP +++ b/sources/Externals/Soundfont/SFREADER.CPP @@ -667,11 +667,27 @@ ReadSFBData (CHAR * pchReqdWaveTable) //// Sample Headers //// for (uiCount = 0; uiCount < hf->awStructSize[sampHdr]; uiCount++) { - - tRIFF.SwapDWORD(&hf->pSHdr[uiCount].dwStart); - tRIFF.SwapDWORD(&hf->pSHdr[uiCount].dwEnd); - tRIFF.SwapDWORD(&hf->pSHdr[uiCount].dwStartloop); - tRIFF.SwapDWORD(&hf->pSHdr[uiCount].dwEndloop); + // For ADDR fields, we need to swap using temporary 32-bit variables + // since the file format uses 32-bit values but we store in pointer-sized + // fields + { + DWORD tmp; + tmp = (DWORD)hf->pSHdr[uiCount].dwStart; + tRIFF.SwapDWORD(&tmp); + hf->pSHdr[uiCount].dwStart = tmp; + + tmp = (DWORD)hf->pSHdr[uiCount].dwEnd; + tRIFF.SwapDWORD(&tmp); + hf->pSHdr[uiCount].dwEnd = tmp; + + tmp = (DWORD)hf->pSHdr[uiCount].dwStartloop; + tRIFF.SwapDWORD(&tmp); + hf->pSHdr[uiCount].dwStartloop = tmp; + + tmp = (DWORD)hf->pSHdr[uiCount].dwEndloop; + tRIFF.SwapDWORD(&tmp); + hf->pSHdr[uiCount].dwEndloop = tmp; + } tRIFF.SwapDWORD(&hf->pSHdr[uiCount].dwSampleRate); @@ -794,25 +810,29 @@ WORD mono = monoSample; // Element for element read of data, to serve as an example of a possible // issue with cross platform code. See comments below. // - for(WORD curHdr=0; curHdr < hf->awStructSize[iHydraSymbol]; curHdr++) { + for (WORD curHdr = 0; curHdr < hf->awStructSize[iHydraSymbol]; curHdr++) { + + rcnt = 0; - rcnt = 0; + rcnt += tRIFF.RIFFRead((VOIDPTR) & + ((sfSampleHdr *)pData)[curHdr].achSampleName, + 1, SAMPLENAMESIZE); - rcnt += tRIFF.RIFFRead((VOIDPTR) - &((sfSampleHdr*)pData)[curHdr].achSampleName, - 1, SAMPLENAMESIZE); + // Read 32-bit file values into temporaries, then assign to + // pointer-sized fields + uint32_t tmp_dwStart, tmp_dwEnd, tmp_dwStartloop, tmp_dwEndloop; - rcnt += tRIFF.RIFFRead( &((sfSampleHdr*)pData)[curHdr].dwStart, - 1, sizeof(DWORD)); + rcnt += tRIFF.RIFFRead(&tmp_dwStart, 1, sizeof(uint32_t)); + ((sfSampleHdr *)pData)[curHdr].dwStart = tmp_dwStart; - rcnt += tRIFF.RIFFRead( &((sfSampleHdr*)pData)[curHdr].dwEnd, - 1, sizeof(DWORD)); + rcnt += tRIFF.RIFFRead(&tmp_dwEnd, 1, sizeof(uint32_t)); + ((sfSampleHdr *)pData)[curHdr].dwEnd = tmp_dwEnd; - rcnt += tRIFF.RIFFRead( &((sfSampleHdr*)pData)[curHdr].dwStartloop, - 1, sizeof(DWORD)); + rcnt += tRIFF.RIFFRead(&tmp_dwStartloop, 1, sizeof(uint32_t)); + ((sfSampleHdr *)pData)[curHdr].dwStartloop = tmp_dwStartloop; - rcnt += tRIFF.RIFFRead( &((sfSampleHdr*)pData)[curHdr].dwEndloop, - 1, sizeof(DWORD)); + rcnt += tRIFF.RIFFRead(&tmp_dwEndloop, 1, sizeof(uint32_t)); + ((sfSampleHdr *)pData)[curHdr].dwEndloop = tmp_dwEndloop; rcnt += tRIFF.RIFFRead( &((sfSampleHdr*)pData)[curHdr].dwSampleRate, 1, sizeof(DWORD)); @@ -837,7 +857,7 @@ WORD mono = monoSample; SetError(SF_INVALIDBANK); return (NULL); } - }// end for all elements + } // end for all elements } // do sampleHdr reading, else { diff --git a/sources/System/FileSystem/FileSystem.cpp b/sources/System/FileSystem/FileSystem.cpp index 168c793f..78884dcc 100644 --- a/sources/System/FileSystem/FileSystem.cpp +++ b/sources/System/FileSystem/FileSystem.cpp @@ -215,4 +215,20 @@ int FileSystemService::Copy(const Path &src,const Path &dst) isrc->Close(); idst->Close(); return nbwrite; +} + +int FileSystemService::Delete(const Path &path) { + int result = -1; + std::string pathString = path.GetPath(); + FileSystem * fs = FileSystem::GetInstance(); + + if (fs->GetFileType(pathString.c_str()) != FT_UNKNOWN) { + fs->Delete(pathString.c_str()); + result += 1; + Trace::Log("FileSystemService"," Delete %s ", pathString.c_str()); + } else { + Trace::Log("FS Delete","path does not exist: %s", pathString.c_str()); + } + + return result; } \ No newline at end of file diff --git a/sources/System/FileSystem/FileSystem.h b/sources/System/FileSystem/FileSystem.h index a915ffc8..4f36388d 100644 --- a/sources/System/FileSystem/FileSystem.h +++ b/sources/System/FileSystem/FileSystem.h @@ -111,6 +111,7 @@ class FileSystem: public T_Factory { class FileSystemService { public: int Copy(const Path &src,const Path &dst); + int Delete(const Path &path); }; #endif