diff --git a/.github/workflows/mtrack.yaml b/.github/workflows/mtrack.yaml
index 5058995..a6f0123 100644
--- a/.github/workflows/mtrack.yaml
+++ b/.github/workflows/mtrack.yaml
@@ -95,7 +95,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install licensure
- run: cargo install licensure@0.3.2
+ run: cargo install licensure@0.5.1
- name: Check for licenses
run: licensure --check -p
diff --git a/.gitignore b/.gitignore
index 323c058..e19e8ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
target
.vscode
cobertura.xml
+lcov.info
diff --git a/.licensure.yml b/.licensure.yml
index 3802ddd..cd8673b 100644
--- a/.licensure.yml
+++ b/.licensure.yml
@@ -11,13 +11,26 @@ excludes:
- .*\.yaml
- .*\.wav
- .*\.mid
+ - lcov.info
licenses:
- files: any
ident: GPL-3.0
authors:
- name: Michael Wilson
email: mike@mdwn.dev
- auto_template: true
+ template: |+
+ Copyright (C) [year] [name of author]
+
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free Software
+ Foundation, version 3.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
comments:
- columns: 80
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7850f2..5c42571 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+The expiration mechanism for cancellation had an unintended side effect
+of preventing cancellation if one component of the song completed ahead
+of time. In other words, if a MIDI file finished playing but there is
+still audio to play, the song is no longer cancellable. Additionally, it
+would be possible, in some circumstances, for the completion of one
+aspect of a song to cancel others unexpectedly.
+
+The "expiration" concept was introduced to allow cancellation while
+still allowing a song to finish normally. This has been replaced with a
+simple concept of an atomic bool that indicates whether a song component
+(MIDI, DMX, or audio) has finished and, when used in combination with
+the new "notify" function, will allow a cancel\_handle.wait() call to
+return without an actual cancellation happening.
+
## [0.1.8] - Better MacOS support.
MacOS support is improved. It's not super thoroughly tested, but has been tested
diff --git a/Cargo.lock b/Cargo.lock
index 620c32c..673af42 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "addr2line"
@@ -99,9 +99,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.94"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
+checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "autocfg"
@@ -139,7 +139,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -174,9 +174,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cc"
-version = "1.2.4"
+version = "1.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
+checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333"
dependencies = [
"jobserver",
"libc",
@@ -246,7 +246,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -353,9 +353,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@@ -372,9 +372,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "dasp_sample"
@@ -412,9 +412,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fixedbitset"
-version = "0.4.2"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "futures"
@@ -472,7 +472,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -513,15 +513,15 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
-version = "0.15.2"
+version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
@@ -537,11 +537,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
-version = "0.5.9"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -552,9 +552,9 @@ checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
[[package]]
name = "indexmap"
-version = "2.7.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
+checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
@@ -639,9 +639,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.168"
+version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libloading"
@@ -724,9 +724,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.8.0"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
]
@@ -829,7 +829,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -859,14 +859,14 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
name = "object"
-version = "0.36.5"
+version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
@@ -945,9 +945,9 @@ dependencies = [
[[package]]
name = "petgraph"
-version = "0.6.5"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
+checksum = "c94eb96835f05ec51384814c9b2daef83f68486f67a0e2e9680e0f698dca808e"
dependencies = [
"fixedbitset",
"indexmap",
@@ -1055,9 +1055,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.37"
+version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@@ -1177,22 +1177,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
-version = "1.0.216"
+version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.216"
+version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -1250,9 +1250,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spin_sleep"
-version = "1.2.1"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64bd7227d85bfd1b8df51e0d83da36d9baaee85eb75730386ef8e3ab6f2a2ea3"
+checksum = "4196b31c8c1dc443543be4f4d0e827657fbf2b87387e5c8f229b14f1c046718a"
dependencies = [
"windows-sys 0.59.0",
]
@@ -1276,9 +1276,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.90"
+version = "2.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
+checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
dependencies = [
"proc-macro2",
"quote",
@@ -1315,7 +1315,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -1347,7 +1347,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -1386,7 +1386,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -1479,7 +1479,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
"wasm-bindgen-shared",
]
@@ -1514,7 +1514,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1628,7 +1628,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -1639,7 +1639,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.90",
+ "syn 2.0.93",
]
[[package]]
@@ -1660,15 +1660,6 @@ dependencies = [
"windows-targets 0.42.2",
]
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
[[package]]
name = "windows-sys"
version = "0.59.0"
diff --git a/src/audio/mock.rs b/src/audio/mock.rs
index fb08798..6f37a8d 100644
--- a/src/audio/mock.rs
+++ b/src/audio/mock.rs
@@ -70,8 +70,10 @@ impl super::Device for Device {
let (sleep_tx, sleep_rx) = mpsc::channel::<()>();
self.is_playing.store(true, Ordering::Relaxed);
+ let finished = Arc::new(AtomicBool::new(false));
let join_handle = {
let cancel_handle = cancel_handle.clone();
+ let finished = finished.clone();
// Wait until the song is cancelled or until the song is done.
thread::spawn(move || {
play_barrier.wait();
@@ -80,11 +82,12 @@ impl super::Device for Device {
let _ = sleep_rx.recv_timeout(song.duration);
// Expire at the end of playback.
- cancel_handle.expire();
+ finished.store(true, Ordering::Relaxed);
+ cancel_handle.notify();
})
};
- cancel_handle.wait();
+ cancel_handle.wait(finished);
sleep_tx.send(())?;
let join_result = join_handle.join();
diff --git a/src/config.rs b/src/config.rs
index 0816488..d788fa1 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -141,11 +141,10 @@ pub fn init_player_and_controller(
crate::playlist::Playlist::from_songs(songs)?,
status_events,
);
- let controller = crate::controller::Controller::new(
+ crate::controller::Controller::new(
player,
player_config.controller.driver(midi_device.clone())?,
- );
- controller
+ )
}
fn get_songs_path(player_path: &PathBuf, songs: String) -> PathBuf {
diff --git a/src/dmx/engine.rs b/src/dmx/engine.rs
index d81287c..ade51a3 100644
--- a/src/dmx/engine.rs
+++ b/src/dmx/engine.rs
@@ -151,7 +151,6 @@ impl Engine {
play_barrier.wait();
player.play(&dmx_midi_sheet.sheet);
- cancel_handle.expire();
play_finished.store(true, std::sync::atomic::Ordering::Relaxed);
})
})
@@ -173,8 +172,6 @@ impl Engine {
.expect("Empty barrier join handle should join immediately");
});
- cancel_handle.wait();
-
if cancel_handle.is_cancelled() {
info!("DMX playback has been cancelled.");
}
diff --git a/src/midi/midir.rs b/src/midi/midir.rs
index a1d50ad..df31ba8 100644
--- a/src/midi/midir.rs
+++ b/src/midi/midir.rs
@@ -17,7 +17,10 @@ use std::{
error::Error,
fmt, mem,
ops::Add,
- sync::{Arc, Barrier, Mutex},
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc, Barrier, Mutex,
+ },
thread,
time::{self, Instant},
};
@@ -140,8 +143,10 @@ impl super::Device for Device {
"Playing song MIDI."
);
+ let finished = Arc::new(AtomicBool::new(false));
let join_handle = {
let cancel_handle = cancel_handle.clone();
+ let finished = finished.clone();
// Wrap the midir connection in a cancel connection so that we can stop playback.
let midir_connection = output.connect(output_port, "mtrack player")?;
@@ -158,11 +163,12 @@ impl super::Device for Device {
thread::spawn(move || {
play_barrier.wait();
player.play(&midi_sheet.sheet);
- cancel_handle.expire();
+ finished.store(true, Ordering::Relaxed);
+ cancel_handle.notify();
})
};
- cancel_handle.wait();
+ cancel_handle.wait(finished);
if cancel_handle.is_cancelled() {
info!("MIDI playback has been cancelled.");
diff --git a/src/midi/mock.rs b/src/midi/mock.rs
index 6a18444..2d46655 100644
--- a/src/midi/mock.rs
+++ b/src/midi/mock.rs
@@ -139,8 +139,10 @@ impl super::Device for Device {
let (sleep_tx, sleep_rx) = mpsc::channel::<()>();
+ let finished = Arc::new(AtomicBool::new(false));
let join_handle = {
let cancel_handle = cancel_handle.clone();
+ let finished = finished.clone();
// Wait until the song is cancelled or until the song is done.
thread::spawn(move || {
play_barrier.wait();
@@ -148,11 +150,12 @@ impl super::Device for Device {
let _ = sleep_rx.recv_timeout(song.duration);
// Expire at the end of playback.
- cancel_handle.expire();
+ finished.store(true, Ordering::Relaxed);
+ cancel_handle.notify();
})
};
- cancel_handle.wait();
+ cancel_handle.wait(finished);
sleep_tx.send(())?;
if join_handle.join().is_err() {
return Err("Error while joining thread!".into());
diff --git a/src/playsync.rs b/src/playsync.rs
index 5b7495b..c674a02 100644
--- a/src/playsync.rs
+++ b/src/playsync.rs
@@ -11,14 +11,13 @@
// You should have received a copy of the GNU General Public License along with
// this program. If not, see .
//
-use std::sync::{Arc, Condvar, Mutex};
+use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, Condvar, Mutex};
/// Represents the current cancel state.
#[derive(PartialEq)]
enum CancelState {
Untouched,
Cancelled,
- Expired,
}
/// A cancel handle is passed to the device during a play operation. It's the player's responsibility
@@ -45,25 +44,23 @@ impl CancelHandle {
*self.cancelled.lock().expect("Error getting lock") == CancelState::Cancelled
}
- /// Waits for the cancel handle to expire or be cancelled.
- pub fn wait(&self) {
+ /// Waits for the cancel handle to be cancelled or for finished to be set to true.
+ pub fn wait(&self, finished: Arc) {
let _unused = self
.condvar
.wait_while(
self.cancelled.lock().expect("Error getting lock"),
- |cancelled| *cancelled == CancelState::Untouched,
+ |cancelled| {
+ *cancelled == CancelState::Untouched && !finished.load(Ordering::Relaxed)
+ },
)
.expect("Error getting lock");
}
- /// Expire the cancel handle. This will let all active cancel handle waits proceed without
- /// setting the handle to cancelled.
- pub fn expire(&self) {
- let mut cancel_state = self.cancelled.lock().expect("Error getting lock");
- if *cancel_state == CancelState::Untouched {
- *cancel_state = CancelState::Expired;
- self.condvar.notify_all();
- }
+ /// Notifies the cancel handle to see if this the song has been cancelled or if the
+ /// particular element has finished.
+ pub fn notify(&self) {
+ self.condvar.notify_all();
}
/// Cancel the device process.
@@ -71,7 +68,7 @@ impl CancelHandle {
let mut cancel_state = self.cancelled.lock().expect("Error getting lock");
if *cancel_state == CancelState::Untouched {
*cancel_state = CancelState::Cancelled;
- self.condvar.notify_all();
+ self.notify();
}
}
}
@@ -89,7 +86,7 @@ mod test {
let join = {
let cancel_handle = cancel_handle.clone();
- thread::spawn(move || cancel_handle.wait())
+ thread::spawn(move || cancel_handle.wait(Arc::new(AtomicBool::new(false))))
};
cancel_handle.cancel();
@@ -98,16 +95,15 @@ mod test {
}
#[test]
- fn test_cancel_handle_expired() {
+ fn test_cancel_handle_finished() {
let cancel_handle = CancelHandle::new();
assert!(!cancel_handle.is_cancelled());
let join = {
let cancel_handle = cancel_handle.clone();
- thread::spawn(move || cancel_handle.wait())
+ thread::spawn(move || cancel_handle.wait(Arc::new(AtomicBool::new(true))))
};
- cancel_handle.expire();
assert!(join.join().is_ok());
assert!(!cancel_handle.is_cancelled());
}