Skip to content

Commit

Permalink
added instructions for channel settings
Browse files Browse the repository at this point in the history
  • Loading branch information
prixt committed Mar 5, 2020
1 parent add56ac commit 9ea6139
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 71 deletions.
51 changes: 19 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SOUNDSENSE-RS</br><img src="./icons/icon.ico" width="128px" height="128px"></img></br>SoundSense, written in Rust.</br>![Build](https://github.com/prixt/soundsense-rs/workflows/Build/badge.svg)
# SOUNDSENSE-RS</br><img src="./icons/icon.ico" width="128px" height="128px"></img></br>Soundsense, written in Rust.</br>![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.

<p align='center'>
Expand All @@ -14,64 +14,51 @@ 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.

## 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/)
Expand Down
22 changes: 22 additions & 0 deletions about_channel_setting.md
Original file line number Diff line number Diff line change
@@ -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.<br>Setting for any channels that are not in the soundpack will be ignored. |
| playType | "all"(_default_), "singleEager", "singleLazy" | How the channel will play sounds.<br>__"all"__: will play all sounds. <br>__"singleEager"__: will play one sound at a time, and will pause/stop the current playing sounds when a new sound is triggered. <br>__"singleLazy"__: will play one sound at a time, and will ignore new sounds when already playing a sound. |

Example:
```
<?xml version="1.1" encoding="UTF-8"?>
<channelSettings>
<channelSetting name="music" playType="singleEager"/>
<channelSetting name="weather" playType="singleEager"/>
<channelSetting name="trade" playType="singleLazy"/>
</channelSettings>
```

Attributes may be added/changed in the future.
12 changes: 12 additions & 0 deletions src/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SoundMessage>, ui_tx: Sender<UIMessage>) {
// Outer loop. Restarts the inner loop if an error occured, but didn't panic.
Expand Down
11 changes: 1 addition & 10 deletions src/sound/sound_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,15 @@ 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 {
looping: LoopPlayer,
one_shots: OneshotPlayer,
local_volume: VolumeLock,
delay: usize,
play_type: ChannelPlayType,
local_is_paused: IsPausedLock,
pub play_type: ChannelPlayType,
}

impl SoundChannel {
Expand Down
133 changes: 104 additions & 29 deletions src/sound/sound_manager.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use std::collections::HashMap;

/// The struct that parses the log entries.
/// Plays appropriate sounds on appropriate channels;
Expand Down Expand Up @@ -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<UIMessage>) -> Result<Self> {
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<Box<str>, SoundChannel> = BTreeMap::new();
Expand Down Expand Up @@ -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.");
}
}

// <soundFile> or <soundFile/>
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;
Expand Down Expand Up @@ -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()
);
}

// <channelSetting/>
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<Box<str>> = 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.");
}
}
},

// </Sound>
Expand Down Expand Up @@ -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<Box<str>, 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<()> {
{
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 9ea6139

Please sign in to comment.