Skip to content

Commit

Permalink
Merged from master, prepare for v1.4.6 [ci-build]
Browse files Browse the repository at this point in the history
  • Loading branch information
prixt committed Feb 22, 2020
1 parent db4c7fc commit 5ccbafd
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 79 deletions.
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build = "build.rs"
authors = ["prixt <[email protected]>"]
edition = "2018"
description = "A Rusty SoundSense alternative"
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -26,7 +27,7 @@ env_logger = "0.7.1"

[build-dependencies]
winres = "0.1"
html-minifier = "1.1.14"
html-minifier = "=1.1.9"

[profile.dev.package.rodio]
debug-assertions = false
Expand All @@ -39,4 +40,7 @@ incremental = false
panic = "abort"

[profile.release.build-override]
opt-level = 0
opt-level = 0

[package.metadata.winres]
LegalCopyright = "© 2019-2020 prixt"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ My attempt at recreating [SoundSense](http://df.zweistein.cz/soundsense/), a sou

## Why?
1. To see if I could do it.
2. Attempt to create a standalone application that doesn't require bloat (Java VM, C# VM, gtk, etc...).
2. Attempt to create a standalone application that doesn't require bloat.
* Ultimately, you should only need one executable, the soundpack folder, and DF.
* Recommended soundpack fork: https://github.com/jecowa/soundsensepack

Expand Down Expand Up @@ -49,7 +49,7 @@ You can set the following environment variables to set the logging parameters. (
* __SOUNDSENSE_RS_LOG_STYLE__: st the level of the log style. _(always, never; default: always)_

## Dependencies
Linux: libasound2, libgtk-3, libwebkit2gtk-4.0
__Linux__: libasound2, libgtk-3, libgdk-3, libwebkit2gtk-4.0, libjavascriptcregtk-4.0

## [MIT License](./LICENSE)

Expand Down
10 changes: 4 additions & 6 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ fn main() -> Result<(), Box<dyn Error>> {
let index_html = html_minifier::minify(index_html)?;

File::create(dest_dir)?
.write_all( index_html.as_bytes() )?;
.write_all(index_html.as_bytes())?;

#[cfg(target_os="windows")]
{
let mut res = winres::WindowsResource::new();
res.set_icon("icons/icon.ico");
res.compile().unwrap();
}
winres::WindowsResource::new()
.set_icon("icons/icon.ico")
.compile()?;

Ok(())
}
57 changes: 45 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,28 @@ mod sound;
mod ui;
mod message;

/// How SoundSense-RS works:
/// 1. Dwarf Fortress(&DFHack) writes into gamelog.txt
/// 2. In the Sound thread, every loop, the SoundManager reads the newly written lines.
/// 3. The SoundManager iterates through the SoundEntries, and checks if any of their patterns match.
/// 4. If a pattern matches, play the SoundEntry's SoundFiles on the appropriate SoundChannel.
///
/// All the while the UI thread handles user input and send SoundMessage to the SoundThread
/// through a Sender<SoundMessage>, while the Sound thread send UIMessages to the UI through
/// a Sender<UIMessage>.
fn main() {
// Setup and initialize the env_logger.
let env = env_logger::Env::default()
.filter_or("SOUNDSENSE_RS_LOG", "warn")
.write_style_or("SOUNDSENSE_RS_LOG_STYLE", "always");
env_logger::init_from_env(env);
env_logger::Builder::from_env(env)
.format_module_path(false)
.format_timestamp_millis()
.init();
info!("Starting SoundSense-RS");

// Setup getopts style argument handling.
let args: Vec<String> = env::args().collect();
let mut opts = getopts::Options::new();
opts.optopt("l", "gamelog",
Expand All @@ -28,16 +44,19 @@ fn main() {
.optflag("", "no-config",
"Don't read config files on start. Will use the given paths, or soundsense-rs defaults.");

// If there are errors in the arguments, print the usage of SoundSense-RS and quit.
let matches = match opts.parse(&args[1..]) {
Ok(matches) => matches,
Err(e) => {
error!("{}", e);
println!("{}", opts.usage("SoundSense-rs"));
println!("{}", opts.usage("SoundSense-RS"));
return
}
};

let conf = if !matches.opt_present("no-config") {
// Check if there are config files available.
// If so, read `soundsense-rs/default-paths.ini`.
let config = if !matches.opt_present("no-config") {
dirs::config_dir()
.and_then(|mut p| {
p.push("soundsense-rs/default-paths.ini");
Expand All @@ -47,20 +66,24 @@ fn main() {

let gamelog_path = matches
.opt_str("l")
// If a path is given, and is a file, use that as the gamelog.
.and_then(|path| {
let path = PathBuf::from(path);
if path.is_file() {Some(path)} else {None}
})
// Else if config file contains path to the gamelog, use that as the gamelog.
.or_else(||
conf.as_ref()
.and_then(|conf_txt|
config.as_ref()
.and_then(|config_txt|
Regex::new("gamelog=(.+)").unwrap()
.captures(&conf_txt)
.captures(&config_txt)
.and_then(|c| c.get(1))
.map(|m| PathBuf::from(m.as_str()))
.filter(|p| p.is_file())
)
)
// Else try to find `gamelog.txt` in the current working directory.
// Otherwise, just return None.
.or_else(|| {
let mut path = env::current_dir()
.expect("Error finding current working directory.");
Expand All @@ -69,20 +92,24 @@ fn main() {
});
let soundpack_path = matches
.opt_str("p")
// If a path is given, and is a directory, use that as the soundpack.
.and_then(|path| {
let path = PathBuf::from(path);
if path.is_dir() {Some(path)} else {None}
})
// Else if config file contains path to the soundpack, use that as the soundpack.
.or_else(||
conf.as_ref()
.and_then(|conf_txt|
config.as_ref()
.and_then(|config_txt|
Regex::new("soundpack=(.+)").unwrap()
.captures(&conf_txt)
.captures(&config_txt)
.and_then(|c| c.get(1))
.map(|m| PathBuf::from(m.as_str()))
.filter(|p| p.is_dir())
)
)
// Else try to find `soundpack` directory in the current working directory.
// Otherwise, just return None.
.or_else(|| {
let mut path = env::current_dir()
.expect("Error finding current working directory.");
Expand All @@ -91,20 +118,24 @@ fn main() {
});
let ignore_path = matches
.opt_str("i")
// If a path is given, and is a file, use that as the ignore list.
.and_then(|path| {
let path = PathBuf::from(path);
if path.is_file() {Some(path)} else {None}
})
// Else if config file contains path to the ignore list, use that as the ignore list.
.or_else(||
conf.as_ref()
.and_then(|conf_txt|
config.as_ref()
.and_then(|config_txt|
Regex::new("ignore=(.+)").unwrap()
.captures(&conf_txt)
.captures(&config_txt)
.and_then(|c| c.get(1))
.map(|m| PathBuf::from(m.as_str()))
.filter(|p| p.is_file())
)
)
// Else try to find `ignore.txt` in the current working directory.
// Otherwise, just return None.
.or_else(|| {
let mut path = env::current_dir()
.expect("Error finding current working directory.");
Expand All @@ -115,8 +146,10 @@ fn main() {
let (sound_tx, sound_rx) = channel();
let (ui_tx, ui_rx) = channel();

// Build and spawn the Sound thread.
std::thread::Builder::new()
.name("sound_thread".to_string())
.spawn(move || sound::run(sound_rx, ui_tx)).unwrap();
// Start the UI thread.
ui::run(sound_tx, ui_rx, gamelog_path, soundpack_path, ignore_path);
}
16 changes: 14 additions & 2 deletions src/message.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
/// Messages sent from the UI thread to the Sound thread.
#[non_exhaustive]
pub enum SoundMessage {
/// Reload the gamelog with this path.
ChangeGamelog(std::path::PathBuf),
/// Reload the soundpack with this path.
ChangeSoundpack(std::path::PathBuf),
/// Reload the ignore list with this path.
ChangeIgnoreList(std::path::PathBuf),
/// Change the volume of a channel.
/// "all" is total volume, other values are specific channels.
VolumeChange(Box<str>, f32),
// SetCurrentPathsAsDefault,
/// Store the current channels volumes to a config file.
SetCurrentVolumesAsDefault(std::fs::File),
}

#[allow(dead_code)]
/// Message sent from the Sound thread to the UI thread.
#[non_exhaustive]
pub enum UIMessage {
/// The gamelog finished loading.
LoadedGamelog,
/// The soundpack finished loading.
/// Contains the names of the loaded channels.
LoadedSoundpack(Vec<Box<str>>),
/// The ignore list finished loading.
LoadedIgnoreList,
/// Loaded the default volumes from config.
LoadedVolumeSettings(Vec<(Box<str>,f32)>),
/// There was an error in the Sound thread.
SoundThreadPanicked(String,String),
}
23 changes: 21 additions & 2 deletions src/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ lazy_static! {
).unwrap();
}

/// Show if the SoundFile is a single sound, or a playlist of multiple sounds.
#[derive(Clone)]
pub enum SoundFileType {
/// Contains a single file path.
IsPath(PathBuf),
/// Contains multiple file paths.
IsPlaylist(Vec<PathBuf>)
}

/// A struct containing all the information about a SoundFile.
#[derive(Clone)]
pub struct SoundFile {
pub r#type: SoundFileType, // path to audio file with sound. OR list of paths
Expand All @@ -48,6 +52,8 @@ pub struct SoundFile {
pub balance: f32, // adjusts stereo channel, can range for -1 (full left) to 1 (full right).
}

/// A thread-safe wrapper around a volume(f32) volume.
/// Intended to be used by LoopPlayers and OneshotPlayers.
#[derive(Clone)]
pub struct VolumeLock(Arc<RwLock<f32>>);
impl VolumeLock {
Expand All @@ -62,6 +68,7 @@ impl VolumeLock {
}
}

/// A struct containing all the information about a Sound, such as regex patterns, channel, loopability, etc.
pub struct SoundEntry {
pub pattern: regex::Regex, // regular expression matching log line
pub channel: Option<Box<str>>, // channel on which sound is played. sounds played on channel can be looped/stopped prematurely
Expand All @@ -79,14 +86,23 @@ pub struct SoundEntry {
pub recent_call: usize,
}

/// The sound thread function.
pub fn run(sound_rx: Receiver<SoundMessage>, ui_tx: Sender<UIMessage>) {
// Outer loop. Restarts the inner loop if an error occured, but didn't panic.
loop {
info!("(Re)Starting sound thread.");
// SoundManager
let mut manager : Option<SoundManager> = None;
// BufReader for the gamelog.
let mut buf_reader : Option<BufReader<File>> = None;
// Current time for delta time calculation.
let mut prev = Instant::now();

// Arguably the most front-heavy if statement I ever wrote.
if let Err(error) = || -> Result<()> {
// Inner loop. Will return an Error if something wrong happens.
loop {
// Read SoundMessages sent from the UI.
for message in sound_rx.try_iter() {
use SoundMessage::*;
match message {
Expand All @@ -101,6 +117,7 @@ pub fn run(sound_rx: Receiver<SoundMessage>, ui_tx: Sender<UIMessage>) {
manager = Some(SoundManager::new(&path, ui_tx.clone())?);
}

// These types of messages require a manager.
message => if let Some(manager) = manager.as_mut() {
match message {
ChangeIgnoreList(path) => {
Expand Down Expand Up @@ -142,7 +159,9 @@ pub fn run(sound_rx: Receiver<SoundMessage>, ui_tx: Sender<UIMessage>) {
}
prev = current;
}
}(){ // Arguably the most front-heavy if statement I ever wrote.
}(){// LOOK, A BUTTERFLY!
// If an error occurred and was caugth, send the error message to the UI.
// Return to the outer loop, which will then restart the inner loop.
let mut error_message = "The soundthread restarted due to this error:\n".to_string();
error_message.push_str(&error.to_string());
ui_tx.send(
Expand All @@ -151,7 +170,7 @@ pub fn run(sound_rx: Receiver<SoundMessage>, ui_tx: Sender<UIMessage>) {
error_message,
)
).unwrap();
error!("SoundThreadError: {:?}", error);
error!("SoundThreadError:\n{:?}", error);
}
}
}
Loading

0 comments on commit 5ccbafd

Please sign in to comment.