Compare commits
5 Commits
3dba8213d6
...
5310b98542
| Author | SHA1 | Date | |
|---|---|---|---|
| 5310b98542 | |||
| 7099501130 | |||
| 730ddfb716 | |||
| 0a186f774c | |||
| 25a5c77344 |
@@ -1,5 +1,10 @@
|
||||
# Uncomment to use local doux for development
|
||||
paths = ["/Users/bubo/doux"]
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --release --"
|
||||
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
rustflags = ["-C", "link-args=-lstdc++ -lws2_32 -liphlpapi -lwinmm"]
|
||||
rustflags = [
|
||||
"-C", "link-args=-Wl,-Bstatic -lstdc++ -lgcc -lgcc_eh -lpthread -Wl,-Bdynamic -lmingwex -lmsvcrt -lws2_32 -liphlpapi -lwinmm -lole32 -loleaut32 -luuid -lkernel32",
|
||||
]
|
||||
|
||||
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -141,6 +141,21 @@ jobs:
|
||||
name: ${{ matrix.artifact }}-desktop
|
||||
path: target/${{ matrix.target }}/release/cagire-desktop.exe
|
||||
|
||||
- name: Install cargo-wix (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: cargo install cargo-wix
|
||||
|
||||
- name: Build MSI installer (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: cargo wix --no-build --nocapture -C -p -C x64
|
||||
|
||||
- name: Upload MSI installer (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact }}-msi
|
||||
path: target/wix/*.msi
|
||||
|
||||
- name: Upload CLAP artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -266,6 +281,18 @@ jobs:
|
||||
-output cagire-plugins.vst3/Contents/MacOS/cagire-plugins
|
||||
lipo -info cagire-plugins.vst3/Contents/MacOS/cagire-plugins
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
assets/DMG-README.txt
|
||||
scripts/make-dmg.sh
|
||||
clean: false
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
chmod +x scripts/make-dmg.sh
|
||||
scripts/make-dmg.sh Cagire.app .
|
||||
|
||||
- name: Build .pkg installer
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
@@ -303,6 +330,12 @@ jobs:
|
||||
name: cagire-macos-universal-vst3
|
||||
path: cagire-plugins.vst3
|
||||
|
||||
- name: Upload DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cagire-macos-universal-dmg
|
||||
path: Cagire-*.dmg
|
||||
|
||||
- name: Upload .pkg installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -328,7 +361,9 @@ jobs:
|
||||
mkdir -p release
|
||||
for dir in artifacts/*/; do
|
||||
name=$(basename "$dir")
|
||||
if [[ "$name" == "cagire-macos-universal-pkg" ]]; then
|
||||
if [[ "$name" == "cagire-macos-universal-dmg" ]]; then
|
||||
cp "$dir"/*.dmg release/
|
||||
elif [[ "$name" == "cagire-macos-universal-pkg" ]]; then
|
||||
cp "$dir"/*.pkg release/
|
||||
elif [[ "$name" == "cagire-macos-universal-desktop" ]]; then
|
||||
cp "$dir/Cagire.app.zip" "release/cagire-macos-universal-desktop.app.zip"
|
||||
@@ -344,6 +379,8 @@ jobs:
|
||||
elif [[ "$name" == *-vst3 ]]; then
|
||||
base="${name%-vst3}"
|
||||
cd "$dir" && zip -r "../../release/${base}-vst3.zip" cagire-plugins.vst3 && cd ../..
|
||||
elif [[ "$name" == *-msi ]]; then
|
||||
cp "$dir"/*.msi release/
|
||||
elif [[ "$name" == *-appimage ]]; then
|
||||
cp "$dir"/*.AppImage release/
|
||||
elif [[ "$name" == *-desktop ]]; then
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1809,8 +1809,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "doux"
|
||||
version = "0.0.4"
|
||||
source = "git+https://github.com/sova-org/doux#ba475601a448f36d4755eb1fdcaa629987485892"
|
||||
version = "0.0.5"
|
||||
source = "git+https://github.com/sova-org/doux#886702b4fe937d26ed681a2f6d7626d26d6890d0"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"clap",
|
||||
|
||||
19
assets/DMG-README.txt
Normal file
19
assets/DMG-README.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Cagire - A Forth-based music sequencer
|
||||
Made by BuboBubo and his friends
|
||||
======================================
|
||||
|
||||
Installation
|
||||
------------
|
||||
Drag Cagire.app into the Applications folder.
|
||||
|
||||
Unquarantine
|
||||
------------
|
||||
Since this app is not signed with an Apple Developer certificate,
|
||||
macOS will block it from running. To fix this, open Terminal and run:
|
||||
|
||||
xattr -cr /Applications/Cagire.app
|
||||
|
||||
Support
|
||||
-------
|
||||
If you enjoy Cagire, consider supporting development:
|
||||
https://ko-fi.com/raphaelbubo
|
||||
7
build.rs
7
build.rs
@@ -4,10 +4,13 @@ fn main() {
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
|
||||
if target_os == "windows" {
|
||||
// rusty_link's build.rs only links stdc++ on linux — add it for windows too
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
// C++ runtime (stdc++, gcc, gcc_eh, pthread) linked statically via .cargo/config.toml
|
||||
// using -Wl,-Bstatic. Only Windows system DLLs go here.
|
||||
println!("cargo:rustc-link-lib=ws2_32");
|
||||
println!("cargo:rustc-link-lib=iphlpapi");
|
||||
println!("cargo:rustc-link-lib=winmm");
|
||||
println!("cargo:rustc-link-lib=ole32");
|
||||
println!("cargo:rustc-link-lib=oleaut32");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"nand" => Op::Nand,
|
||||
"nor" => Op::Nor,
|
||||
"ifelse" => Op::IfElse,
|
||||
"pick" => Op::Pick,
|
||||
"select" => Op::Pick,
|
||||
"sound" => Op::NewCmd,
|
||||
"." => Op::Emit,
|
||||
"rand" => Op::Rand(None),
|
||||
|
||||
@@ -507,12 +507,12 @@ pub(super) const WORDS: &[Word] = &[
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "pick",
|
||||
name: "select",
|
||||
aliases: &[],
|
||||
category: "Logic",
|
||||
stack: "(..quots n --)",
|
||||
desc: "Execute nth quotation (0-indexed)",
|
||||
example: "{ 1 } { 2 } { 3 } 2 pick => 3",
|
||||
example: "{ 1 } { 2 } { 3 } 2 select => 3",
|
||||
compile: Simple,
|
||||
varargs: true,
|
||||
},
|
||||
|
||||
@@ -186,6 +186,26 @@ pub(super) const WORDS: &[Word] = &[
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "slice",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Divide sample into N equal slices",
|
||||
example: r#""break" s 8 slice 3 pick ."#,
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "pick",
|
||||
aliases: &[],
|
||||
category: "Sample",
|
||||
stack: "(v.. --)",
|
||||
desc: "Select which slice to play (0-indexed, wraps)",
|
||||
example: r#""break" s 8 slice 3 pick ."#,
|
||||
compile: Param,
|
||||
varargs: true,
|
||||
},
|
||||
Word {
|
||||
name: "voice",
|
||||
aliases: &[],
|
||||
|
||||
@@ -45,6 +45,8 @@ snare sound 0.5 speed . ( play snare at half speed )
|
||||
| `n` | 0+ | Sample index within a folder (wraps around) |
|
||||
| `begin` | 0-1 | Playback start position |
|
||||
| `end` | 0-1 | Playback end position |
|
||||
| `slice` | 1+ | Divide sample into N equal slices |
|
||||
| `pick` | 0+ | Select which slice to play (0-indexed, wraps) |
|
||||
| `speed` | any | Playback speed multiplier |
|
||||
| `freq` | Hz | Base frequency for pitch tracking |
|
||||
| `fit` | seconds | Stretch/compress sample to fit duration |
|
||||
@@ -62,6 +64,21 @@ kick sound 0.5 end . ( play first half )
|
||||
|
||||
If begin is greater than end, they swap automatically.
|
||||
|
||||
## Slice and Pick
|
||||
|
||||
For evenly-spaced slicing, `slice` divides the sample into N equal parts and `pick` selects which one (0-indexed, wraps around).
|
||||
|
||||
```forth
|
||||
break sound 8 slice 3 pick . ( play the 4th eighth of the sample )
|
||||
break sound 16 slice step pick . ( scan through 16 slices by step )
|
||||
```
|
||||
|
||||
Combine with `fit` to time-stretch each slice to a target duration. `fit` accounts for the sliced range automatically.
|
||||
|
||||
```forth
|
||||
break sound 4 slice 2 pick 1 loop . ( quarter of the sample, fitted to 1 beat )
|
||||
```
|
||||
|
||||
## Speed and Pitch
|
||||
|
||||
The `speed` parameter affects both tempo and pitch. A speed of 2 plays twice as fast and an octave higher.
|
||||
|
||||
@@ -63,12 +63,12 @@ Reads naturally: "c3 or c4, depending on the coin."
|
||||
tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise
|
||||
```
|
||||
|
||||
## pick
|
||||
## select
|
||||
|
||||
Choose the nth option from a list of quotations:
|
||||
|
||||
```forth
|
||||
{ c4 } { e4 } { g4 } { b4 } iter 4 mod pick
|
||||
{ c4 } { e4 } { g4 } { b4 } iter 4 mod select
|
||||
note sine s 0.5 decay .
|
||||
```
|
||||
|
||||
|
||||
@@ -229,6 +229,14 @@ bundle_plugins_native() {
|
||||
cargo xtask bundle "$PLUGIN_NAME" --release $tf
|
||||
}
|
||||
|
||||
bundle_desktop_native() {
|
||||
local platform="$1"
|
||||
local tf
|
||||
tf=$(target_flag "$platform")
|
||||
# shellcheck disable=SC2086
|
||||
cargo bundle --release --features desktop --bin cagire-desktop $tf
|
||||
}
|
||||
|
||||
bundle_plugins_cross() {
|
||||
local platform="$1"
|
||||
local rd
|
||||
@@ -300,6 +308,25 @@ copy_artifacts() {
|
||||
local dst="$OUT/cagire-desktop-${os}-${arch}${suffix}"
|
||||
cp "$src" "$dst"
|
||||
echo " cagire-desktop -> $dst"
|
||||
|
||||
# macOS .app bundle
|
||||
if [[ "$os" == "macos" ]]; then
|
||||
local app_src="$rd/bundle/osx/Cagire.app"
|
||||
if [[ -d "$app_src" ]]; then
|
||||
local app_dst="$OUT/Cagire-${arch}.app"
|
||||
rm -rf "$app_dst"
|
||||
cp -R "$app_src" "$app_dst"
|
||||
echo " Cagire.app -> $app_dst"
|
||||
scripts/make-dmg.sh "$app_dst" "$OUT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# MSI installer for Windows targets
|
||||
if [[ "$os" == "windows" ]] && command -v cargo-wix &>/dev/null; then
|
||||
echo " Building MSI installer..."
|
||||
cargo wix --no-build --nocapture -C -p -C x64
|
||||
cp target/wix/*.msi "$OUT/" 2>/dev/null && echo " MSI -> $OUT/" || true
|
||||
fi
|
||||
|
||||
# AppImage for Linux targets
|
||||
@@ -407,6 +434,10 @@ for platform in "${selected_platforms[@]}"; do
|
||||
if $build_desktop; then
|
||||
echo " -> cagire-desktop"
|
||||
build_binary "$platform" --features desktop --bin cagire-desktop
|
||||
if ! is_cross_target "$platform"; then
|
||||
echo " -> bundling cagire-desktop .app"
|
||||
bundle_desktop_native "$platform"
|
||||
fi
|
||||
fi
|
||||
|
||||
if $build_plugins; then
|
||||
|
||||
@@ -7,7 +7,13 @@ RUN apt-get update && \
|
||||
libclang-dev \
|
||||
mingw-w64-tools \
|
||||
mingw-w64-x86-64-dev \
|
||||
g++-mingw-w64-x86-64 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -sf windows.h /usr/x86_64-w64-mingw32/include/Windows.h \
|
||||
&& ln -sf winsock2.h /usr/x86_64-w64-mingw32/include/WinSock2.h \
|
||||
&& ln -sf ws2tcpip.h /usr/x86_64-w64-mingw32/include/WS2tcpip.h
|
||||
&& ln -sf ws2tcpip.h /usr/x86_64-w64-mingw32/include/WS2tcpip.h \
|
||||
&& GCCDIR=$(ls -d /usr/lib/gcc/x86_64-w64-mingw32/*-posix 2>/dev/null | head -1) \
|
||||
&& ln -sf "$GCCDIR/libstdc++.a" /usr/x86_64-w64-mingw32/lib/libstdc++.a \
|
||||
&& ln -sf "$GCCDIR/libgcc.a" /usr/x86_64-w64-mingw32/lib/libgcc.a \
|
||||
&& rm -f /usr/x86_64-w64-mingw32/lib/libstdc++.dll.a \
|
||||
&& rm -f /usr/lib/gcc/x86_64-w64-mingw32/*/libstdc++.dll.a
|
||||
|
||||
52
scripts/make-dmg.sh
Executable file
52
scripts/make-dmg.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Usage: scripts/make-dmg.sh <app-path> <output-dir>
|
||||
# Produces a .dmg from a macOS .app bundle using only hdiutil.
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Usage: $0 <app-path> <output-dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_PATH="$1"
|
||||
OUTDIR="$2"
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
|
||||
if [[ ! -d "$APP_PATH" ]]; then
|
||||
echo "ERROR: $APP_PATH is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LIPO_OUTPUT=$(lipo -info "$APP_PATH/Contents/MacOS/cagire-desktop" 2>/dev/null)
|
||||
|
||||
if [[ -z "$LIPO_OUTPUT" ]]; then
|
||||
echo "ERROR: could not determine architecture from $APP_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "$LIPO_OUTPUT" | grep -q "Architectures in the fat file"; then
|
||||
ARCH="universal"
|
||||
else
|
||||
ARCH=$(echo "$LIPO_OUTPUT" | awk '{print $NF}')
|
||||
case "$ARCH" in
|
||||
arm64) ARCH="aarch64" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
STAGING="$(mktemp -d)"
|
||||
trap 'rm -rf "$STAGING"' EXIT
|
||||
|
||||
cp -R "$APP_PATH" "$STAGING/Cagire.app"
|
||||
ln -s /Applications "$STAGING/Applications"
|
||||
cp "$REPO_ROOT/assets/DMG-README.txt" "$STAGING/README.txt"
|
||||
|
||||
DMG_NAME="Cagire-${ARCH}.dmg"
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
hdiutil create -volname "Cagire" \
|
||||
-srcfolder "$STAGING" \
|
||||
-ov -format UDZO \
|
||||
"$OUTDIR/$DMG_NAME"
|
||||
|
||||
echo " DMG -> $OUTDIR/$DMG_NAME"
|
||||
@@ -394,6 +394,7 @@ impl eframe::App for CagireDesktop {
|
||||
|
||||
self.app.flush_queued_changes(&sequencer.cmd_tx);
|
||||
self.app.flush_dirty_patterns(&sequencer.cmd_tx);
|
||||
self.app.flush_dirty_script(&sequencer.cmd_tx);
|
||||
|
||||
while let Ok(midi_cmd) = self.midi_rx.try_recv() {
|
||||
match midi_cmd {
|
||||
|
||||
@@ -1070,7 +1070,7 @@ impl SequencerState {
|
||||
}
|
||||
|
||||
let script_frontier = if self.script_frontier < 0.0 {
|
||||
frontier.max(0.0)
|
||||
frontier
|
||||
} else {
|
||||
self.script_frontier
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@ use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use crate::commands::AppCommand;
|
||||
use crate::page::Page;
|
||||
use crate::state::{
|
||||
DeviceKind, DictFocus, EngineSection, HelpFocus, MinimapMode, Modal, OptionsFocus,
|
||||
PatternsColumn, SettingKind,
|
||||
DeviceKind, DictFocus, EditorTarget, EngineSection, HelpFocus, MinimapMode, Modal,
|
||||
OptionsFocus, PatternsColumn, SettingKind,
|
||||
};
|
||||
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view, script_view};
|
||||
|
||||
@@ -94,14 +94,22 @@ fn handle_editor_drag(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_editor_mouse(ctx: &mut InputContext, col: u16, row: u16, term: Rect, dragging: bool) {
|
||||
// Reconstruct editor area (mirrors render_modal_editor / ModalFrame::render_centered)
|
||||
fn editor_modal_rect(term: Rect) -> Rect {
|
||||
let width = (term.width * 80 / 100).max(40);
|
||||
let height = (term.height * 60 / 100).max(10);
|
||||
let modal_w = width.min(term.width.saturating_sub(4));
|
||||
let modal_h = height.min(term.height.saturating_sub(4));
|
||||
let mx = term.x + (term.width.saturating_sub(modal_w)) / 2;
|
||||
let my = term.y + (term.height.saturating_sub(modal_h)) / 2;
|
||||
Rect::new(mx, my, modal_w, modal_h)
|
||||
}
|
||||
|
||||
fn handle_editor_mouse(ctx: &mut InputContext, col: u16, row: u16, term: Rect, dragging: bool) {
|
||||
let modal = editor_modal_rect(term);
|
||||
let mx = modal.x;
|
||||
let my = modal.y;
|
||||
let modal_w = modal.width;
|
||||
let modal_h = modal.height;
|
||||
// inner = area inside 1-cell border
|
||||
let inner_x = mx + 1;
|
||||
let inner_y = my + 1;
|
||||
@@ -180,6 +188,18 @@ fn handle_scroll(ctx: &mut InputContext, col: u16, row: u16, term: Rect, up: boo
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(ctx.app.ui.modal, Modal::Editor) {
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
let code = if up { KeyCode::Up } else { KeyCode::Down };
|
||||
for _ in 0..3 {
|
||||
ctx.app
|
||||
.editor_ctx
|
||||
.editor
|
||||
.input(KeyEvent::new(code, KeyModifiers::empty()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if !matches!(ctx.app.ui.modal, Modal::None) {
|
||||
return;
|
||||
}
|
||||
@@ -1057,13 +1077,28 @@ fn handle_engine_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect, k
|
||||
fn handle_modal_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
|
||||
match &ctx.app.ui.modal {
|
||||
Modal::Editor => {
|
||||
let modal_area = editor_modal_rect(term);
|
||||
if contains(modal_area, col, row) {
|
||||
handle_editor_mouse(ctx, col, row, term, false);
|
||||
} else {
|
||||
match ctx.app.editor_ctx.target {
|
||||
EditorTarget::Step => {
|
||||
ctx.dispatch(AppCommand::SaveEditorToStep);
|
||||
ctx.dispatch(AppCommand::CompileCurrentStep);
|
||||
}
|
||||
EditorTarget::Prelude => {
|
||||
ctx.dispatch(AppCommand::SavePrelude);
|
||||
ctx.dispatch(AppCommand::EvaluatePrelude);
|
||||
ctx.dispatch(AppCommand::ClosePreludeEditor);
|
||||
}
|
||||
}
|
||||
ctx.dispatch(AppCommand::CloseModal);
|
||||
}
|
||||
}
|
||||
Modal::Confirm { .. } => {
|
||||
handle_confirm_click(ctx, col, row, term);
|
||||
}
|
||||
Modal::KeybindingsHelp { .. } => {
|
||||
// Click outside keybindings help to dismiss
|
||||
let padded = padded(term);
|
||||
let width = (padded.width * 80 / 100).clamp(60, 100);
|
||||
let height = (padded.height * 80 / 100).max(15);
|
||||
@@ -1073,7 +1108,20 @@ fn handle_modal_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// For other modals, don't dismiss on click (they have their own input)
|
||||
let (w, h) = match &ctx.app.ui.modal {
|
||||
Modal::PatternProps { .. } => (50, 18),
|
||||
Modal::EuclideanDistribution { .. } => (50, 11),
|
||||
Modal::Onboarding { .. } => (57, 20),
|
||||
Modal::FileBrowser(_) | Modal::AddSamplePath(_) => (60, 18),
|
||||
Modal::Rename { .. } => (40, 5),
|
||||
Modal::SetPattern { .. } | Modal::SetScript { .. } => (45, 5),
|
||||
Modal::SetTempo(_) | Modal::JumpToStep(_) => (30, 5),
|
||||
_ => return,
|
||||
};
|
||||
let modal_area = centered_rect(term, w, h);
|
||||
if !contains(modal_area, col, row) {
|
||||
ctx.dispatch(AppCommand::CloseModal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,21 +196,21 @@ fn ifelse_false() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_first() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 0 pick", 10);
|
||||
fn select_first() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 0 select", 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_second() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 1 pick", 20);
|
||||
fn select_second() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 1 select", 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_third() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 2 pick", 30);
|
||||
fn select_third() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 2 select", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_preserves_stack() {
|
||||
expect_int("5 { 10 } { 20 } 0 pick +", 15);
|
||||
fn select_preserves_stack() {
|
||||
expect_int("5 { 10 } { 20 } 0 select +", 15);
|
||||
}
|
||||
|
||||
@@ -255,3 +255,16 @@ fn all_replaces_previous_global() {
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert!(outputs[0].contains("lpf/2000"), "latest lpf should be 2000: {}", outputs[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slice_param() {
|
||||
let outputs = expect_outputs(r#""break" s 8 slice ."#, 1);
|
||||
assert!(outputs[0].contains("slice/8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_param() {
|
||||
let outputs = expect_outputs(r#""break" s 8 slice 3 pick ."#, 1);
|
||||
assert!(outputs[0].contains("slice/8"));
|
||||
assert!(outputs[0].contains("pick/3"));
|
||||
}
|
||||
|
||||
20
wix/License.rtf
Normal file
20
wix/License.rtf
Normal file
@@ -0,0 +1,20 @@
|
||||
{\rtf1\ansi\deff0\nouicompat{\fonttbl{\f0\fswiss\fcharset0 Helvetica;}}
|
||||
{\*\generator Msftedit 5.41.21.2510;}\viewkind4\uc1
|
||||
\pard\sa200\sl276\slmult1\f0\fs20\lang9
|
||||
|
||||
CAGIRE - Forth-based Music Sequencer\par
|
||||
Copyright (c) 2025 Rapha\"el Forment\par
|
||||
\par
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.\par
|
||||
\par
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.\par
|
||||
\par
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see https://www.gnu.org/licenses/.\par
|
||||
}
|
||||
146
wix/main.wxs
Normal file
146
wix/main.wxs
Normal file
@@ -0,0 +1,146 @@
|
||||
<?xml version='1.0' encoding='windows-1252'?>
|
||||
|
||||
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = intel64 ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?endif ?>
|
||||
|
||||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||
|
||||
<Product
|
||||
Id='*'
|
||||
Name='Cagire'
|
||||
UpgradeCode='F2A3D4E5-6B7C-8D9E-0F1A-2B3C4D5E6F7A'
|
||||
Manufacturer='Raphael Forment'
|
||||
Language='1033'
|
||||
Codepage='1252'
|
||||
Version='$(var.Version)'>
|
||||
|
||||
<Package Id='*'
|
||||
Keywords='Installer'
|
||||
Description='Cagire - Forth-based music sequencer'
|
||||
Manufacturer='Raphael Forment'
|
||||
InstallerVersion='450'
|
||||
Languages='1033'
|
||||
Compressed='yes'
|
||||
InstallScope='perMachine'
|
||||
SummaryCodepage='1252'
|
||||
/>
|
||||
|
||||
<MajorUpgrade
|
||||
Schedule='afterInstallInitialize'
|
||||
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
|
||||
|
||||
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
|
||||
<Property Id='DiskPrompt' Value='Cagire Installation'/>
|
||||
|
||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
|
||||
<Directory Id='APPLICATIONFOLDER' Name='Cagire'>
|
||||
|
||||
<Component Id='CagireCLI' Guid='A1B2C3D4-E5F6-7890-ABCD-EF1234567890' Win64='yes'>
|
||||
<File
|
||||
Id='CagireEXE'
|
||||
Name='cagire.exe'
|
||||
DiskId='1'
|
||||
Source='$(var.CargoTargetBinDir)\cagire.exe'
|
||||
KeyPath='yes'/>
|
||||
</Component>
|
||||
|
||||
<Component Id='CagireDesktop' Guid='B2C3D4E5-F6A7-8901-BCDE-F12345678901' Win64='yes'>
|
||||
<File
|
||||
Id='CagireDesktopEXE'
|
||||
Name='cagire-desktop.exe'
|
||||
DiskId='1'
|
||||
Source='$(var.CargoTargetBinDir)\cagire-desktop.exe'
|
||||
KeyPath='yes'/>
|
||||
</Component>
|
||||
|
||||
<Component Id='PathEntry' Guid='C3D4E5F6-A7B8-9012-CDEF-123456789012' Win64='yes' KeyPath='yes'>
|
||||
<Environment
|
||||
Id='PATH'
|
||||
Name='PATH'
|
||||
Value='[APPLICATIONFOLDER]'
|
||||
Permanent='no'
|
||||
Part='last'
|
||||
Action='set'
|
||||
System='yes'/>
|
||||
</Component>
|
||||
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Directory Id='ProgramMenuFolder'>
|
||||
<Directory Id='ApplicationProgramsFolder' Name='Cagire'>
|
||||
<Component Id='StartMenuShortcut' Guid='D4E5F6A7-B8C9-0123-DEFA-234567890123' Win64='yes'>
|
||||
<Shortcut
|
||||
Id='CagireDesktopShortcut'
|
||||
Name='Cagire'
|
||||
Description='Forth-based music sequencer'
|
||||
Target='[APPLICATIONFOLDER]cagire-desktop.exe'
|
||||
WorkingDirectory='APPLICATIONFOLDER'
|
||||
Icon='CagireIcon.exe'/>
|
||||
<RemoveFolder Id='CleanUpShortcutFolder' On='uninstall'/>
|
||||
<RegistryValue
|
||||
Root='HKCU'
|
||||
Key='Software\Cagire'
|
||||
Name='installed'
|
||||
Type='integer'
|
||||
Value='1'
|
||||
KeyPath='yes'/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature
|
||||
Id='Binaries'
|
||||
Title='Application'
|
||||
Description='Installs Cagire CLI and Desktop binaries.'
|
||||
Level='1'
|
||||
ConfigurableDirectory='APPLICATIONFOLDER'
|
||||
AllowAdvertise='no'
|
||||
Display='expand'
|
||||
Absent='disallow'>
|
||||
|
||||
<ComponentRef Id='CagireCLI'/>
|
||||
<ComponentRef Id='CagireDesktop'/>
|
||||
|
||||
<Feature
|
||||
Id='Environment'
|
||||
Title='PATH Environment Variable'
|
||||
Description='Add the install location to the PATH system environment variable. This allows the cagire CLI to be called from any location.'
|
||||
Level='1'
|
||||
Absent='allow'>
|
||||
<ComponentRef Id='PathEntry'/>
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id='StartMenu'
|
||||
Title='Start Menu Shortcut'
|
||||
Description='Add a Cagire shortcut to the Start Menu.'
|
||||
Level='1'
|
||||
Absent='allow'>
|
||||
<ComponentRef Id='StartMenuShortcut'/>
|
||||
</Feature>
|
||||
|
||||
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
|
||||
|
||||
<Icon Id='CagireIcon.exe' SourceFile='assets\Cagire.ico'/>
|
||||
<Property Id='ARPPRODUCTICON' Value='CagireIcon.exe'/>
|
||||
<Property Id='ARPHELPLINK' Value='https://cagire.raphaelforment.fr'/>
|
||||
<Property Id='ARPURLINFOABOUT' Value='https://github.com/Bubobubobubobubo/cagire'/>
|
||||
|
||||
<UI>
|
||||
<UIRef Id='WixUI_FeatureTree'/>
|
||||
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
|
||||
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
|
||||
</UI>
|
||||
|
||||
<WixVariable Id='WixUILicenseRtf' Value='wix\License.rtf'/>
|
||||
|
||||
</Product>
|
||||
|
||||
</Wix>
|
||||
Reference in New Issue
Block a user