mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-03 08:50:21 -06:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f8e788cb | ||
|
|
5afc2aab66 |
@@ -1,6 +1,4 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
/// A MIDI note. Times are in model ticks ([`crate::PPQ`] per quarter note).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
@@ -110,28 +108,14 @@ impl Sequence {
|
||||
|
||||
self.ctrls.sort_by_key(|c| c.tick);
|
||||
let mut kept: Vec<CtrlEvent> = Vec::with_capacity(self.ctrls.len());
|
||||
// The latest tick and `kept` index of each channel+kind, so a same-tick repeat is found
|
||||
// without rescanning the tick's events (a tick can hold many thousands of distinct keys).
|
||||
// An entry from an earlier tick counts as absent; keeping the tick instead of clearing the
|
||||
// map per tick keeps the work per event constant after a dense tick.
|
||||
let mut last_at: HashMap<(u8, (u8, u8)), (i64, usize)> = HashMap::new();
|
||||
for c in self.ctrls.drain(..) {
|
||||
// Within a tick, replace an earlier event of the same channel+kind.
|
||||
match last_at.entry((c.channel, kind_key(c.kind))) {
|
||||
Entry::Occupied(mut e) => {
|
||||
let (tick, idx) = *e.get();
|
||||
match kept.get_mut(idx) {
|
||||
Some(slot) if tick == c.tick => *slot = c,
|
||||
_ => {
|
||||
e.insert((c.tick, kept.len()));
|
||||
kept.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert((c.tick, kept.len()));
|
||||
kept.push(c);
|
||||
}
|
||||
// Only events at the same tick (the tail of `kept`) can match.
|
||||
let slot =
|
||||
kept.iter_mut().rev().take_while(|k| k.tick == c.tick).find(|k| k.channel == c.channel && kind_key(k.kind) == kind_key(c.kind));
|
||||
match slot {
|
||||
Some(slot) => *slot = c,
|
||||
None => kept.push(c),
|
||||
}
|
||||
}
|
||||
self.ctrls = kept;
|
||||
|
||||
@@ -222,72 +222,3 @@ fn sequence_serde_round_trip() {
|
||||
let json = serde_json::to_string(&seq).unwrap();
|
||||
assert_eq!(serde_json::from_str::<Sequence>(&json).unwrap(), seq);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_duplicates_handles_a_large_same_tick_controller_group() {
|
||||
// Every channel x controller number at one tick, twice: the second copy of each wins. A
|
||||
// rescan of the tick's kept events per incoming event would make this quadratic.
|
||||
let keys: Vec<(u8, u8)> = (0..=255u8).flat_map(|ch| (0..=255u8).map(move |cc| (ch, cc))).collect();
|
||||
let ctrls = [0, 1]
|
||||
.iter()
|
||||
.flat_map(|&pass| keys.iter().map(move |&(channel, cc)| CtrlEvent { tick: 7, channel, kind: CtrlKind::Cc(cc), value: pass }))
|
||||
.collect();
|
||||
let mut seq = Sequence { notes: Vec::new(), ctrls };
|
||||
let start = std::time::Instant::now();
|
||||
assert_eq!(seq.remove_duplicates(), keys.len());
|
||||
assert!(start.elapsed() < std::time::Duration::from_secs(10), "{:?}", start.elapsed());
|
||||
assert_eq!(seq.ctrls.len(), keys.len());
|
||||
assert!(seq.ctrls.iter().all(|c| c.value == 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_duplicates_matches_a_per_tick_rescan_on_mixed_ticks() {
|
||||
// The straightforward per-tick rescan the indexed version replaced, kept as an oracle.
|
||||
fn reference(mut ctrls: Vec<CtrlEvent>) -> Vec<CtrlEvent> {
|
||||
ctrls.sort_by_key(|c| c.tick);
|
||||
let key = |k: CtrlKind| match k {
|
||||
CtrlKind::Cc(n) => (0, n),
|
||||
CtrlKind::PitchBend => (1, 0),
|
||||
CtrlKind::ChannelPressure => (2, 0),
|
||||
CtrlKind::PolyPressure(p) => (3, p),
|
||||
CtrlKind::Program => (4, 0),
|
||||
};
|
||||
let mut kept: Vec<CtrlEvent> = Vec::new();
|
||||
for c in ctrls {
|
||||
match kept.iter_mut().rev().take_while(|k| k.tick == c.tick).find(|k| k.channel == c.channel && key(k.kind) == key(c.kind)) {
|
||||
Some(slot) => *slot = c,
|
||||
None => kept.push(c),
|
||||
}
|
||||
}
|
||||
kept
|
||||
}
|
||||
|
||||
// Few ticks, channels and kinds, so the same key recurs both within a tick (interleaved with
|
||||
// other keys) and across ticks; unsorted input from a fixed xorshift stream.
|
||||
let mut state = 0x9E37_79B9_7F4A_7C15u64;
|
||||
let mut next = move || {
|
||||
state ^= state << 13;
|
||||
state ^= state >> 7;
|
||||
state ^= state << 17;
|
||||
state
|
||||
};
|
||||
let kinds = |r: u64| match r % 5 {
|
||||
0 => CtrlKind::Cc((r >> 8) as u8 % 4),
|
||||
1 => CtrlKind::PitchBend,
|
||||
2 => CtrlKind::ChannelPressure,
|
||||
3 => CtrlKind::PolyPressure((r >> 8) as u8 % 4),
|
||||
_ => CtrlKind::Program,
|
||||
};
|
||||
for round in 0..200 {
|
||||
let mut ctrls = Vec::new();
|
||||
for i in 0..(next() % 64) {
|
||||
let r = next();
|
||||
ctrls.push(CtrlEvent { tick: (r % 6) as i64 - 2, channel: (r >> 16) as u8 % 3, kind: kinds(r >> 24), value: (round * 100 + i) as i32 });
|
||||
}
|
||||
let mut seq = Sequence { notes: Vec::new(), ctrls: ctrls.clone() };
|
||||
let removed = seq.remove_duplicates();
|
||||
let expected = reference(ctrls.clone());
|
||||
assert_eq!(seq.ctrls, expected, "round {round}");
|
||||
assert_eq!(removed, ctrls.len() - expected.len(), "round {round}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,12 +47,6 @@ done
|
||||
|
||||
echo "==> SoundCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
|
||||
|
||||
# The packaged app unpacks to tens of MB; an archive that lists far more would fill the build
|
||||
# host's disk before anything here could check it.
|
||||
MAX_UNPACKED_BYTES=$((2 * 1024 * 1024 * 1024))
|
||||
UNPACKED="$(tar -tzvf "$TARBALL" | awk '{ s += $3 } END { print s + 0 }')"
|
||||
[ "$UNPACKED" -le "$MAX_UNPACKED_BYTES" ] || { echo "error: $TARBALL unpacks to $UNPACKED bytes, more than $MAX_UNPACKED_BYTES" >&2; exit 1; }
|
||||
|
||||
WORK="$CARGO_TARGET_DIR/flatpak-bundle"
|
||||
rm -rf "$WORK"
|
||||
mkdir -p "$WORK/stage"
|
||||
|
||||
@@ -19,13 +19,10 @@ fi
|
||||
|
||||
TMP="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
|
||||
KEYCHAIN="$TMP/soundcraft-signing.keychain-db"
|
||||
# The decoded certificate goes in a folder of this run's own (mktemp creates it, owner-only):
|
||||
# a name another account could plant in the shared folder must not receive or redirect it.
|
||||
CERT_DIR="$(mktemp -d "$TMP/soundcraft-signing.XXXXXX")"
|
||||
CERT="$CERT_DIR/soundcraft-signing.p12"
|
||||
CERT="$TMP/soundcraft-signing.p12"
|
||||
KC_PASS="${KEYCHAIN_PASSWORD:-$(openssl rand -hex 24)}"
|
||||
|
||||
trap 'rm -rf "$CERT_DIR"' EXIT
|
||||
trap 'rm -f "$CERT"' EXIT
|
||||
printf '%s' "$APPLE_CERTIFICATE" | base64 --decode >"$CERT"
|
||||
security create-keychain -p "$KC_PASS" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
|
||||
Reference in New Issue
Block a user