From 9ea613988cad7423f9fe11529cb15ed9ece030fe Mon Sep 17 00:00:00 2001 From: "paraxite@naver.com" Date: Thu, 5 Mar 2020 17:31:16 +0900 Subject: [PATCH] added instructions for channel settings --- README.md | 51 ++++++-------- about_channel_setting.md | 22 ++++++ src/sound.rs | 12 ++++ src/sound/sound_channel.rs | 11 +-- src/sound/sound_manager.rs | 133 +++++++++++++++++++++++++++++-------- 5 files changed, 158 insertions(+), 71 deletions(-) create mode 100644 about_channel_setting.md diff --git a/README.md b/README.md index c87be86..e55137b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SOUNDSENSE-RS

SoundSense, written in Rust.
![Build](https://github.com/prixt/soundsense-rs/workflows/Build/badge.svg) +# SOUNDSENSE-RS

Soundsense, written in Rust.
![Build](https://github.com/prixt/soundsense-rs/workflows/Build/badge.svg) My attempt at recreating [SoundSense](http://df.zweistein.cz/soundsense/), a sound-engine utility for [Dwarf Fortress](http://www.bay12games.com/dwarves/), using Rust.

@@ -14,31 +14,34 @@ My attempt at recreating [SoundSense](http://df.zweistein.cz/soundsense/), a sou * Recommended soundpack fork: https://github.com/jecowa/soundsensepack ## Current Features -* Plays sound reactive to what happens in DF. +* Plays sounds reactive to what happens in DF. * Can adjust sound volumes realtime, by channel. -* Most sound parameters from the original (stereo balance, random balance, etc.) +* Skip and pause sound loops, by channel. +* Supports most sound parameters used by the original Soundsense (stereo balance, random balance, etc.) * Custom ignore list, allowing user to customize which log patterns to ignore. +* Additional soundpack parameters. (Channel Settings) * Simple and Clean GUI. * Low memory requirement. -## Command line parameters +## Command line arguments * __-l / --gamelog [GAMELOG_FILE] :__ preload the gamelog _(default: ".\gamelog.txt")_ * __-p / --soundpack [PACK_DIR] :__ preload the soundpack _(default: ".\soundpack")_ * __-i / --ignore [IGNORE_FILE] :__ preload the ignore list _(default: ".\ignore.txt")_ - * The ignore list is a simple text file, with each line being a regular expression. Any gamelog entries that match an expression in the ignore list will not be parsed. * __--no-config :__ Don't read config files on start. Will use the given paths, or soundsense-rs defaults. Example: - - soundsense-rs.exe -l "path/to/gamelog.txt" -p "path/to/soundpack/folder" +``` +soundsense-rs.exe -l "path/to/gamelog.txt" -p "path/to/soundpack/folder" +``` This will make soundsense-rs check if there is a file named "ignore.txt" in the current working directory, and will use that file to make the ignore list. ## Ignore List -Each line in the ignore list file is considered an regex pattern. If a gamelog message matches any of the patterns, that message is ignored. +Each line in the ignore list file is considered a regex pattern. If a gamelog message matches any of the patterns, that message is ignored. Example: - - (.+) cancels (.+): (.*)(Water|water)(.*)\. +``` +(.+) cancels (.+): (.*)(Water|water)(.*)\. +``` This pattern will make soundsense-rs ignore any cancallations related to water. The regex pattern uses the [regex crate](https://docs.rs/regex/) syntax. @@ -46,32 +49,16 @@ The regex pattern uses the [regex crate](https://docs.rs/regex/) syntax. ## Logging You can set the following environment variables to set the logging parameters. (Disabled on Windows releases) * __SOUNDSENSE_RS_LOG__: set the level of logging. _(trace, debug, info, warn, error; default: warn)_ -* __SOUNDSENSE_RS_LOG_STYLE__: set the level of the log style. _(always, never; default: always)_ +* __SOUNDSENSE_RS_LOG_STYLE__: set the level of the log style. _(always, auto, never; default: auto)_ + +## Channel Settings +[Read about it here.](./about_channel_setting.md) ## Dependencies __Linux__: libasound2, libgtk-3, libgdk-3, libwebkit2gtk-4.0, libjavascriptcregtk-4.0 -## [MIT License](./LICENSE) - -Copyright (c) prixt - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +## MIT License +[Read it here.](./LICENSE) ## CSS Resources * [W3.CSS](https://www.w3schools.com/w3css/) diff --git a/about_channel_setting.md b/about_channel_setting.md new file mode 100644 index 0000000..f129576 --- /dev/null +++ b/about_channel_setting.md @@ -0,0 +1,22 @@ +# How to set Channel Settings +At the root of your soundpack directory, create an xml file. +While the name can be arbitrary, `channelSettings.xml` is recommended. + +This file should contain one `channelSettings`, which contain multiple `channelSetting`. + +| Attribute | Possible Values | Description | +| -- | -- | ----------- | +| name | _channel_name_ (__required__) | Channel's name.
Setting for any channels that are not in the soundpack will be ignored. | +| playType | "all"(_default_), "singleEager", "singleLazy" | How the channel will play sounds.
__"all"__: will play all sounds.
__"singleEager"__: will play one sound at a time, and will pause/stop the current playing sounds when a new sound is triggered.
__"singleLazy"__: will play one sound at a time, and will ignore new sounds when already playing a sound. | + +Example: +``` + + + + + + +``` + +Attributes may be added/changed in the future. \ No newline at end of file diff --git a/src/sound.rs b/src/sound.rs index 5c25d94..31c5958 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -128,6 +128,18 @@ pub struct SoundEntry { pub recent_call: usize, } +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq)] +pub enum ChannelPlayType { + All, + SingleEager, + SingleLazy, +} + +pub struct ChannelSetting { + play_type: ChannelPlayType, +} + /// The sound thread function. pub fn run(sound_rx: Receiver, ui_tx: Sender) { // Outer loop. Restarts the inner loop if an error occured, but didn't panic. diff --git a/src/sound/sound_channel.rs b/src/sound/sound_channel.rs index 48f411d..a60bbfc 100644 --- a/src/sound/sound_channel.rs +++ b/src/sound/sound_channel.rs @@ -7,15 +7,6 @@ mod oneshot_player; use loop_player::LoopPlayer; use oneshot_player::OneshotPlayer; -#[allow(dead_code)] -#[non_exhaustive] -#[derive(PartialEq)] -enum ChannelPlayType { - All, - SingleEager, - SingleLazy, -} - /// Struct responsible for containing currently playing sounds. /// "music" and "weather" channels can play only one sound at a time. pub struct SoundChannel { @@ -23,8 +14,8 @@ pub struct SoundChannel { one_shots: OneshotPlayer, local_volume: VolumeLock, delay: usize, - play_type: ChannelPlayType, local_is_paused: IsPausedLock, + pub play_type: ChannelPlayType, } impl SoundChannel { diff --git a/src/sound/sound_manager.rs b/src/sound/sound_manager.rs index 5c6ef28..5a59387 100644 --- a/src/sound/sound_manager.rs +++ b/src/sound/sound_manager.rs @@ -1,4 +1,5 @@ use super::*; +use std::collections::HashMap; /// The struct that parses the log entries. /// Plays appropriate sounds on appropriate channels; @@ -30,10 +31,12 @@ pub struct SoundManager { impl SoundManager { /// Create a new manager. /// A new manager is created every time the user reloads a soundpack. + #[allow(clippy::cognitive_complexity)] pub fn new(sound_dir: &Path, ui_sender: Sender) -> Result { let total_volume = VolumeLock::new(); let total_is_paused = IsPausedLock::new(); - let mut sounds = Vec::new(); + let mut sounds = Vec::new(); + let mut channel_settings = None; let device = default_output_device() .ok_or("Failed to get default audio output device.")?; let mut channels : BTreeMap, SoundChannel> = BTreeMap::new(); @@ -160,39 +163,43 @@ impl SoundManager { } trace!(" SoundEntry"); - let pattern = pattern.ok_or_else(|| - format!("A SoundEntry in {:?} doesn't have a pattern!", file_path) - )?; - trace!(" -Pattern: {}", pattern); - current_sound = Some( - SoundEntry{ - pattern, - channel, - loop_attr, - concurency, - timeout, - probability, - delay, - halt_on_match, - random_balance, - playback_threshold, - files, - weights, - current_timeout: 0, - recent_call: 0, - } - ); + if let Some(pattern) = pattern { + trace!(" -Pattern: {}", pattern); + current_sound = Some( + SoundEntry{ + pattern, + channel, + loop_attr, + concurency, + timeout, + probability, + delay, + halt_on_match, + random_balance, + playback_threshold, + files, + weights, + current_timeout: 0, + recent_call: 0, + } + ); + } + else { + warn!("A SoundEntry in {:?} doesn't have a pattern!", file_path); + warn!("Will ignore this SoundEntry."); + } } // or else if local_name == b"soundFile" { - current_sound.as_ref().ok_or_else(|| - format!("A SoundFile in {:?} was declared outside of Sound!", file_path) - )?; + if current_sound.is_none() { + warn!("A SoundFile in {:?} was declared outside of a valid Sound!", file_path); + warn!("Will ignore this SoundFile."); + } let mut path = PathBuf::from(file_path); let mut is_playlist = false; - let mut weight: f32 = 100.0; - let mut volume: f32 = 1.0; + let mut weight: f32 = 100.0; + let mut volume: f32 = 1.0; let mut random_balance: bool = false; let mut balance: f32 = 0.0; let mut delay: usize = 0; @@ -250,6 +257,60 @@ impl SoundManager { sound.files.push(sound_file); sound.weights.push(weight); } + + else if local_name == b"channelSettings" { + trace!(" ChannelSettings"); + channel_settings = Some( + HashMap::new() + ); + } + + // + else if local_name == b"channelSetting" { + if channel_settings.is_none() { + warn!("A ChannelSetting in {:?} was declared outside of ChannelSettings!", file_path); + warn!("Will ignore this ChannelSetting."); + continue; + } + trace!(" -ChannelSetting"); + let mut name: Option> = None; + let mut play_type = ChannelPlayType::All; + for attr in data.attributes() { + let attr = attr?; + let attr_value = unsafe {std::str::from_utf8_unchecked(&attr.value)}; + match attr.key { + b"name" => { + trace!(" --name: {}", attr_value); + name.replace(Box::from(attr_value)); + } + b"playType" => { + trace!(" --play_type: {}", attr_value); + match attr_value { + "singleEager" => play_type = ChannelPlayType::SingleEager, + "singleLazy" => play_type = ChannelPlayType::SingleLazy, + "all" => (), + other => { + warn!("Unknown Channel PlayType: {}", other); + warn!("Will ignore this value."); + }, + } + } + _ => () + } + } + if let Some(name) = name { + let channel_setting = ChannelSetting { + play_type, + }; + channel_settings.as_mut() + .unwrap() + .insert(name, channel_setting); + } + else { + warn!("A ChannelSetting is {:?} didn't specify a channel name.", file_path); + warn!("Will ignore this ChannelSetting."); + } + } }, // @@ -309,12 +370,26 @@ impl SoundManager { conf_path.push("soundsense-rs/default-volumes.ini"); if conf_path.is_file() { // Check if there are default volumes. let file = fs::File::open(conf_path)?; + // Apply default volumes. manager.get_default_volume(file)?; } + // Apply channels settings if it exists. + if let Some(channel_settings) = channel_settings { + manager.apply_channel_settings(channel_settings); + } Ok(manager) } + /// Apply ChannelSettings. + fn apply_channel_settings(&mut self, channel_settings: HashMap, ChannelSetting>) { + for (name, setting) in channel_settings.iter() { + if let Some(channel) = self.channels.get_mut(name) { + channel.play_type = setting.play_type; + } + } + } + /// Tick down timers on recently called SoundEntries. Maintain the channels. pub fn maintain(&mut self, dt: usize) -> Result<()> { { @@ -394,8 +469,8 @@ impl SoundManager { Ok(()) } - #[allow(clippy::cognitive_complexity)] // You flag this functions, but not `SoundManager::new`?! /// Process one line of log message, and make channels play/pause/stop sounds appropriately. + #[allow(clippy::cognitive_complexity)] pub fn process_log(&mut self, log: &str) -> Result<()> { trace!("log: {}", log); let mut log = log;