Repository: sockheadrps/rpaudio (language: python)

Issue:
Exceptions for changes vol and speed during application of scheduled …
added rpaudio.EffectConflictException for attempting to change speed or volume while schedule effects are actively being applied.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index b37641b..7e1073c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,4 @@ dockerfile
 
 __pycache__/
 .pytest_cache/
+temp/
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 99347a7..740c16b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rpaudio"
-version = "0.0.5"
+version = "0.0.6"
 edition = "2021"
 
 [dependencies]
@@ -10,6 +10,9 @@ pyo3 = { version = "0.22.2", features = ["full", "abi3-py38"] }
 rodio = "0.19.0"
 hound = "3.5.1"
 alsa = {version = "0.9.1", optional = true }
+serde = { version = "1.0.210", features = ["derive"] }
+serde_json = "1.0.128"
+rust_decimal = "1.36.0"
 
 
 [lib]
diff --git a/README.md b/README.md
index b84f3f4..65232f6 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,9 @@
 
 **Python 3.8+**
 
+### Example Projects
+**[FastAPI LAN Audio Web Client](https://github.com/sockheadrps/RpaudioFastAPIExample)**
+
 ## Getting Started ([Read the Docs](https://sockheadrps.github.io/rpaudio/))
 
 ```py
diff --git a/examples/AudioChannel/multi_channels.py b/examples/AudioChannel/multi_channels.py
new file mode 100644
index 0000000..638a143
--- /dev/null
+++ b/examples/AudioChannel/multi_channels.py
@@ -0,0 +1,98 @@
+import rpaudio
+from rpaudio import FadeIn, FadeOut
+import asyncio
+from datetime import datetime, timedelta
+
+
+"""
+This snippet is a test for the rpaudio module. It demonstrates control over audio playback using a single AudioChannel class.
+"""
+
+complete_1: bool = False
+complete_2: bool = False
+
+
+def on_audio_stop() -> None:
+    print("Audio has stopped")
+
+
+async def play_audio(channel_1, channel_2) -> None:
+    channel_1.auto_consume = True
+    channel_2.auto_consume = True
+    await asyncio.sleep(0.1)
+
+    channel_1.current_audio.pause()
+    channel_2.current_audio.pause()
+    channel_1.current_audio.set_volume(0.5)
+    channel_2.current_audio.set_volume(0.5)
+
+    await asyncio.sleep(1)
+    channel_1.current_audio.play()
+
+
+
+
+
+    i = 0
+    while True:
+      await asyncio.sleep(1)
+      i += 1
+
+      if i == 3:
+        channel_1.current_audio.pause()
+      
+      if i == 4:
+        channel_2.current_audio.pause()
+
+      if i == 5:
+        channel_1.current_audio.play()
+        channel_2.current_audio.play()
+
+      if i == 6:
+         channel_1.current_audio.set_volume(1.0)
+      
+      if i == 7:
+        channel_2.current_audio.set_volume(1.0)
+
+      if i == 8:
+        channel_1.current_audio.stop()
+          
+        
+        
+
+
+
+async def sleep_loop() -> None:
+    global complete_1, complete_2
+    while True:
+        print("Sleeping...")
+        if complete_1 and complete_2:
+            break
+        await asyncio.sleep(1)
+
+
+async def main() -> None:
+    AUDIO_FILE = r"C:\Users\16145\Desktop\exc.mp3"
+    AUDIO_FILE_2 = r"C:\Users\16145\Desktop\a2.mp3"
+
+    audio_1 = rpaudio.AudioSink(
+        callback=on_audio_stop).load_audio(AUDIO_FILE)
+    audio_2 = rpaudio.AudioSink(
+        callback=on_audio_stop).load_audio(AUDIO_FILE_2)
+    channel_l = rpaudio.AudioChannel()
+    channel_l.push(audio_1)
+    channel_l.push(audio_2)
+
+    audio_3 = rpaudio.AudioSink(
+        callback=on_audio_stop).load_audio(AUDIO_FILE)
+    audio_4 = rpaudio.AudioSink(
+        callback=on_audio_stop).load_audio(AUDIO_FILE_2)
+    channel_2 = rpaudio.AudioChannel()
+    channel_2.push(audio_3)
+    channel_2.push(audio_4)
+
+
+    await asyncio.gather(play_audio(channel_l, channel_2), sleep_loop())
+
+asyncio.run(main())
+vvvvvvv
\ No newline at end of file
diff --git a/examples/AudioChannel/single_channel_effects.py b/examples/AudioChannel/single_channel_effects.py
index a86395d..3e81f99 100644
--- a/examples/AudioChannel/single_channel_effects.py
+++ b/examples/AudioChannel/single_channel_effects.py
@@ -24,6 +24,8 @@ async def play_audio(channel) -> None:
 
     while not complete_1:
         await asyncio.sleep(1)
+        print(f"metadata: {channel.current_audio_data()}")
+
 
         if channel.current_audio is None or not first.is_playing:
             complete_1 = True
diff --git a/examples/AudioChannel/single_channel_example.py b/examples/AudioChannel/single_channel_example.py
index 42f5079..5f44dd3 100644
--- a/examples/AudioChannel/single_channel_example.py
+++ b/examples/AudioChannel/single_channel_example.py
@@ -31,6 +31,7 @@ async def play_audio(channel) -> None:
                 paused_once = True
                 start_time = datetime.now()
             elif paused_once and datetime.now() - start_time > timedelta(seconds=2):
+                print("Stop audio after 2 seconds")
                 channel.current_audio.stop()
                 paused_once = False
                 start_time = datetime.now()
@@ -39,12 +40,14 @@ async def play_audio(channel) -> None:
         await asyncio.sleep(1)
 
         if channel.current_audio is None or not channel.current_audio.is_playing:
+            print("Complete phase 1")
             complete_1 = True
 
     # Wait until complete_1 is True before starting the second phase
     start_time = datetime.now()
     paused_once = False
     while complete_1 and not complete_2:
+        print("Starting phase 2")
         await asyncio.sleep(1)
 
         if channel.current_audio is not None:
@@ -56,6 +59,7 @@ async def play_audio(channel) -> None:
                 paused_once = True
                 start_time = datetime.now()
             elif paused_once and datetime.now() - start_time > timedelta(seconds=2):
+                print("Stop audio after 2 seconds")
                 channel.current_audio.stop()
                 paused_once = False
                 start_time = datetime.now()
@@ -85,7 +89,6 @@ async def main() -> None:
     audio_2.load_audio(r"C:\Users\16145\Desktop\code_24\rpaudio\examples\Acrylic.mp3")
 
     channel_1 = rpaudio.AudioChannel()
-    channel_1.auto_consume = True
     channel_1.push(audio_1)
     channel_1.push(audio_2)
 
diff --git a/examples/AudioSink/apply_effects.py b/examples/AudioSink/apply_effects.py
index c07d742..37f0ec6 100644
--- a/examples/AudioSink/apply_effects.py
+++ b/examples/AudioSink/apply_effects.py
@@ -13,10 +13,11 @@ def on_audio_stop():
 async def play_audio():
     handler = rpaudio.AudioSink(callback=on_audio_stop).load_audio(AUDIO_FILE)
     print(handler.metadata)
-    handler.try_seek(148.0)
+    # handler.try_seek(148.0)
     await asyncio.sleep(0.3)
+    handler.set_volume(0.0)
 
-    fade_in_effect = FadeIn(duration=3.0, apply_after=0.0)
+    fade_in_effect = FadeIn(duration=10.0, apply_after=0.0)
     fade_out_effect = FadeOut(duration=10.0, apply_after=10.0)
     speed_up = ChangeSpeed(apply_after=5.0, end_val=1.5, duration=3.0)
 
diff --git a/examples/AudioSink/single_audio_sink_example.py b/examples/AudioSink/single_audio_sink_example.py
index c2cefd9..2b568a0 100644
--- a/examples/AudioSink/single_audio_sink_example.py
+++ b/examples/AudioSink/single_audio_sink_example.py
@@ -21,11 +21,13 @@ async def play_audio():
         await asyncio.sleep(1)
 
         count += 1
+        handler.cancel_callback()
+
         if count == 4:
             # Pause the audio for 2 seconds
             print("Pausing audio")
             print(handler.get_volume())
-            handler.pause()
+            handler.stop()
             await asyncio.sleep(1)
 
             print(handler.is_playing)
@@ -34,23 +36,23 @@ async def play_audio():
             # Resume the audio
             handler.play()
 
-        if count == 7:
-            # turn down the volume
-            print("Resuming audio, raise volume")
-            handler.set_volume(0.2)
-            handler.play()
-
-        if count == 8:
-            # Seek to 33.5 seconds
-            print(f"Current position: {handler.get_pos()}")
-            handler.try_seek(10)
-            await asyncio.sleep(1)
-            print(f"Position after seek: {handler.get_pos()}")
-
-        if count == 10:
-            # Stop the audio
-            print(handler.get_volume())
-            handler.stop()
+        # if count == 7:
+        #     # turn down the volume
+        #     print("Resuming audio, raise volume")
+        #     handler.set_volume(0.2)
+        #     handler.play()
+
+        # if count == 8:
+        #     # Seek to 33.5 seconds
+        #     print(f"Current position: {handler.get_pos()}")
+        #     handler.try_seek(10)
+        #     await asyncio.sleep(1)
+        #     print(f"Position after seek: {handler.get_pos()}")
+
+        # if count == 10:
+        #     # Stop the audio
+        #     print(handler.get_volume())
+        #     handler.stop()
 
 
 async def sleep_loop():
diff --git a/pyproject.toml b/pyproject.toml
index 4fe4847..87e671e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "maturin"
 
 [project]
 name = "rpaudio"
-version = "0.0.11"
+version = "0.0.13"
 authors = [
   { name="Ryan Skiles", email="r.p.skiles@gmail.com" },
   { name="Beaux44"}
diff --git a/src/audioqueue.rs b/src/audioqueue.rs
index 9450d58..59a98da 100644
--- a/src/audioqueue.rs
+++ b/src/audioqueue.rs
@@ -2,7 +2,7 @@ use crate::timesync::{self, ActionType};
 use crate::AudioSink;
 use pyo3::exceptions::{PyRuntimeError, PyTypeError};
 use pyo3::prelude::*;
-use pyo3::types::PyList;
+use pyo3::types::{PyDict, PyList};
 use std::sync::{Arc, Mutex};
 use std::time::Duration;
 use std::{fmt, thread};
@@ -19,11 +19,7 @@ pub struct AudioChannel {
 
 impl fmt::Debug for AudioSink {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(
-            f,
-            "AudioSink {{ is_playing: {:?} }}",
-            *self.is_playing.lock().unwrap()
-        )
+        write!(f, "AudioSink {{ is_playing: {:?} }}", self.is_playing)
     }
 }
 
@@ -37,11 +33,13 @@ impl AudioChannel {
             currently_playing: Arc::new(Mutex::new(None)),
             effects_chain: Arc::new(Mutex::new(Vec::new())),
         };
+
         let channel_arc = Arc::new(Mutex::new(channel));
         let channel_clone = Arc::clone(&channel_arc);
 
         thread::spawn(move || {
             let mut backoff = 10;
+
             loop {
                 let channel = channel_clone.lock().unwrap();
 
@@ -61,65 +59,65 @@ impl AudioChannel {
                 };
 
                 if !should_consume {
-                    thread::sleep(Duration::from_millis(500));
+                    thread::sleep(Duration::from_millis(100));
                     continue;
                 }
 
-                if let (Ok(mut playing_guard), Ok(mut queue_guard)) =
-                    (channel.currently_playing.lock(), channel.queue.lock())
                 {
-                    if playing_guard.is_none() && !queue_guard.is_empty() {
-                        let mut next_sink = queue_guard.remove(0);
-                        *playing_guard = Some(next_sink.clone());
-
-                        if let Err(e) = next_sink.play() {
-                            eprintln!("Failed to play sink: {}", e);
-                            *playing_guard = None;
-                        } else {
-                            *playing_guard = Some(next_sink.clone());
-                        }
-
-                        let effects_guard = match channel.effects_chain.lock() {
-                            Ok(guard) => guard,
-                            Err(_) => {
-                                eprintln!("Failed to acquire lock on effects_chain");
+                    if let (Ok(mut playing_guard), Ok(mut queue_guard)) =
+                        (channel.currently_playing.lock(), channel.queue.lock())
+                    {
+                        if playing_guard.is_none() && !queue_guard.is_empty() {
+                            let mut next_sink = queue_guard.remove(0);
+
+                            if let Err(e) = next_sink.play() {
+                                eprintln!("Failed to play sink: {}", e);
                                 continue;
                             }
-                        };
 
-                        if let Some(sender) = next_sink.action_sender.take() {
-                            for effect in effects_guard.iter() {
-                                sender.send(effect.clone()).unwrap();
+                            *playing_guard = Some(next_sink);
+
+                            let effects_guard = match channel.effects_chain.lock() {
+                                Ok(guard) => guard,
+                                Err(_) => {
+                                    eprintln!("Failed to acquire lock on effects_chain");
+                                    continue;
+                                }
+                            };
+
+                            if let Some(sender) =
+                                playing_guard.as_mut().unwrap().action_sender.take()
+                            {
+                                for effect in effects_guard.iter() {
+                                    if let Err(e) = sender.send(effect.clone()) {
+                                        eprintln!("Failed to send effect: {}", e);
+                                    }
+                                }
+                            } else {
+                                eprintln!("Action sender is None");
                             }
-                        } else {
-                            eprintln!("Action sender is None");
-                        }
-
-                        if let Err(e) = next_sink.play() {
-                            eprintln!("Failed to play sink: {}", e);
-                            *playing_guard = None;
-                        } else {
-                            *playing_guard = Some(next_sink.clone());
                         }
+                    } else {
+                        eprintln!("Failed to acquire locks on currently_playing or queue");
                     }
-                } else {
-                    eprintln!("Failed to acquire locks on currently_playing or queue");
-                }
 
-                if let Ok(mut playing_guard) = channel.currently_playing.lock() {
-                    if let Some(ref mut sink) = *playing_guard {
-                        if !sink.is_playing() && sink.empty() {
-                            if let Err(e) = sink.stop() {
-                                eprintln!("Failed to stop sink: {}", e);
+                    if let Ok(mut playing_guard) = channel.currently_playing.lock() {
+                        if let Some(ref mut sink) = *playing_guard {
+                            if !sink.is_playing()
+                                && sink.sink.as_ref().unwrap().lock().unwrap().empty()
+                            {
+                                if let Err(e) = sink.stop() {
+                                    eprintln!("Failed to stop sink: {}", e);
+                                }
+                                *playing_guard = None;
                             }
-                            *playing_guard = None;
                         }
+                    } else {
+                        eprintln!("Failed to acquire lock on currently_playing in _channel_loop()");
                     }
-                } else {
-                    eprintln!("Failed to acquire lock on currently_playing in _channel_loop()");
                 }
 
-                thread::sleep(Duration::from_millis(20));
+                thread::sleep(Duration::from_millis(100));
             }
         });
 
@@ -128,7 +126,7 @@ impl AudioChannel {
     }
 
     pub fn push(&mut self, sink: AudioSink) {
-        if let Ok(mut queue_guard) = self.queue.lock() {
+        if let Ok(mut queue_guard) = self.queue.try_lock() {
             queue_guard.push(sink);
         } else {
             eprintln!("Failed to acquire lock on queue in push()");
@@ -136,7 +134,7 @@ impl AudioChannel {
     }
 
     pub fn pop(&mut self) -> Option<AudioSink> {
-        if let Ok(mut queue_guard) = self.queue.lock() {
+        if let Ok(mut queue_guard) = self.queue.try_lock() {
             queue_guard.pop()
         } else {
             eprintln!("Failed to acquire lock on queue in pop()");
@@ -152,7 +150,7 @@ impl AudioChannel {
 
     #[setter]
     pub fn set_auto_consume(&mut self, value: bool) {
-        if let Ok(mut auto_consume_guard) = self.auto_consume.lock() {
+        if let Ok(mut auto_consume_guard) = self.auto_consume.try_lock() {
             *auto_consume_guard = value;
         } else {
             eprintln!("Failed to acquire lock on auto_consume in set_auto_consume()");
@@ -161,7 +159,7 @@ impl AudioChannel {
 
     #[getter]
     pub fn auto_consume(&self) -> bool {
-        if let Ok(auto_consume_guard) = self.auto_consume.lock() {
+        if let Ok(auto_consume_guard) = self.auto_consume.try_lock() {
             *auto_consume_guard
         } else {
             eprintln!("Failed to acquire lock on auto_consume in auto_consume()");
@@ -174,12 +172,13 @@ impl AudioChannel {
         if let Ok(playing_guard) = self.currently_playing.try_lock() {
             playing_guard.clone()
         } else {
+            println!("Failed to acquire lock on currently_playing in current_audio()");
             None
         }
     }
 
     pub fn drop_current_audio(&mut self) {
-        if let Ok(mut currently_playing_guard) = self.currently_playing.lock() {
+        if let Ok(mut currently_playing_guard) = self.currently_playing.try_lock() {
             if let Some(mut sink) = currently_playing_guard.take() {
                 let _ = sink.stop();
             }
@@ -190,7 +189,7 @@ impl AudioChannel {
 
     #[getter]
     pub fn queue_contents(&self) -> Vec<AudioSink> {
-        if let Ok(queue_guard) = self.queue.lock() {
+        if let Ok(queue_guard) = self.queue.try_lock() {
             queue_guard.clone()
         } else {
             eprintln!("Failed to acquire lock on queue in queue_contents()");
@@ -200,7 +199,7 @@ impl AudioChannel {
 
     #[getter]
     pub fn is_playing(&self) -> bool {
-        if let Ok(currently_playing_guard) = self.currently_playing.lock() {
+        if let Ok(currently_playing_guard) = self.currently_playing.try_lock() {
             if let Some(ref sink) = *currently_playing_guard {
                 let playing_state = sink.is_playing();
                 playing_state
@@ -215,23 +214,16 @@ impl AudioChannel {
 
     #[getter]
     pub fn effects(&self, py: Python) -> PyResult<Py<PyList>> {
-        let effects_guard = self.effects_chain.lock().unwrap();
+        let effects_guard = self.effects_chain.try_lock().unwrap();
+        println!("lock acquired for effects_chain");
 
         let effects_list: Vec<PyObject> = effects_guard
             .iter()
-            .map(|effect| {
-                match effect {
-                    ActionType::FadeIn(fade_in) => {
-                        // Convert Rust FadeIn to Python FadeIn
-                        Py::new(py, fade_in.clone()).unwrap().into_py(py)
-                    }
-                    ActionType::FadeOut(fade_out) => {
-                        // Convert Rust FadeOut to Python FadeOut
-                        Py::new(py, fade_out.clone()).unwrap().into_py(py)
-                    }
-                    ActionType::ChangeSpeed(change_speed) => {
-                        Py::new(py, change_speed.clone()).unwrap().into_py(py)
-                    }
+            .map(|effect| match effect {
+                ActionType::FadeIn(fade_in) => Py::new(py, fade_in.clone()).unwrap().into_py(py),
+                ActionType::FadeOut(fade_out) => Py::new(py, fade_out.clone()).unwrap().into_py(py),
+                ActionType::ChangeSpeed(change_speed) => {
+                    Py::new(py, change_speed.clone()).unwrap().into_py(py)
                 }
             })
             .collect();
@@ -240,41 +232,124 @@ impl AudioChannel {
         Ok(py_list.into())
     }
 
-   pub fn set_effects_chain(&mut self, effect_list: Py<PyList>) -> PyResult<()> {
-    Python::with_gil(|py| {
-        let mut effects_guard = match self.effects_chain.lock() {
-            Ok(guard) => guard,
-            Err(_) => {
-                return Err(PyRuntimeError::new_err(
-                    "Failed to acquire lock on effects_chain",
-                ));
-            }
-        };
-
-        let _effect_list: Vec<Py<PyAny>> = effect_list.extract(py)?;
-
-        let rust_effect_list: Vec<ActionType> = _effect_list
-            .into_iter()
-            .map(|effect| {
-                let effect = effect.downcast_bound(py).unwrap();
-                if let Ok(fade_in) = effect.extract::<FadeIn>() {
-                    Ok(ActionType::FadeIn(fade_in))
-                } else if let Ok(fade_out) = effect.extract::<FadeOut>() {
-                    Ok(ActionType::FadeOut(fade_out))
-                } else if let Ok(change_speed) = effect.extract::<ChangeSpeed>() {
-                    Ok(ActionType::ChangeSpeed(change_speed))
-                } else {
-                    Err(PyTypeError::new_err("Unknown effect type"))
+    pub fn set_effects_chain(&mut self, effect_list: Py<PyList>) -> PyResult<()> {
+        Python::with_gil(|py| {
+            let mut effects_guard = match self.effects_chain.lock() {
+                Ok(guard) => guard,
+                Err(_) => {
+                    return Err(PyRuntimeError::new_err(
+                        "Failed to acquire lock on effects_chain",
+                    ));
                 }
-            })
-            .collect::<Result<Vec<ActionType>, PyErr>>()?;
+            };
+
+            let _effect_list: Vec<Py<PyAny>> = effect_list.extract(py)?;
+
+            let rust_effect_list: Vec<ActionType> = _effect_list
+                .into_iter()
+                .map(|effect| {
+                    let effect = effect.downcast_bound(py).unwrap();
+                    if let Ok(fade_in) = effect.extract::<FadeIn>() {
+                        Ok(ActionType::FadeIn(fade_in))
+                    } else if let Ok(fade_out) = effect.extract::<FadeOut>() {
+                        Ok(ActionType::FadeOut(fade_out))
+                    } else if let Ok(change_speed) = effect.extract::<ChangeSpeed>() {
+                        Ok(ActionType::ChangeSpeed(change_speed))
+                    } else {
+                        Err(PyTypeError::new_err("Unknown effect type"))
+                    }
+                })
+                .collect::<Result<Vec<ActionType>, PyErr>>()?;
 
-        println!("Setting effects chain: {:?}", rust_effect_list);
+            println!("Setting effects chain: {:?}", rust_effect_list);
 
-        *effects_guard = rust_effect_list;
+            *effects_guard = rust_effect_list;
 
-        Ok(())
-    })
-}
+            Ok(())
+        })
+    }
 
+    pub fn current_audio_data(&self) -> PyResult<PyObject> {
+        Python::with_gil(|py| {
+            if let Ok(playing_guard) = self.currently_playing.try_lock() {
+                if let Some(ref sink) = *playing_guard {
+                    let metadata = sink.metadata.clone();
+
+                    let dict = PyDict::new_bound(py);
+                    dict.set_item("album_artist", metadata.album_artist)?;
+                    dict.set_item("album_title", metadata.album_title)?;
+                    dict.set_item("artist", metadata.artist)?;
+                    dict.set_item("channels", metadata.channels)?;
+                    dict.set_item("comment", metadata.comment)?;
+                    dict.set_item("composer", metadata.composer)?;
+                    dict.set_item("date", metadata.date)?;
+                    dict.set_item("disc_number", metadata.disc_number)?;
+                    dict.set_item("duration", metadata.duration)?;
+                    dict.set_item("genre", metadata.genre)?;
+                    dict.set_item("sample_rate", metadata.sample_rate)?;
+                    dict.set_item("title", metadata.title)?;
+                    dict.set_item("total_discs", metadata.total_discs)?;
+                    dict.set_item("total_tracks", metadata.total_tracks)?;
+                    dict.set_item("track_number", metadata.track_number)?;
+                    dict.set_item("year", metadata.year)?;
+                    dict.set_item("speed", sink.get_speed())?;
+                    dict.set_item("position", sink.get_pos()?)?;
+                    dict.set_item("volume", sink.get_volume()?)?;
+
+                    let effects_list: Vec<PyObject> = Vec::new();
+                    let effects_list = PyList::new_bound(py, &effects_list);
+
+                    for effect in self.effects_chain.try_lock().unwrap().iter() {
+                        let effect_dict = PyDict::new_bound(py);
+                        match effect {
+                            ActionType::FadeIn(FadeIn {
+                                duration,
+                                apply_after,
+                                start_val,
+                                end_val,
+                            }) => {
+                                let fadein_dict = PyDict::new_bound(py);
+                                fadein_dict.set_item("duration", duration)?;
+                                fadein_dict.set_item("apply_after", apply_after)?;
+                                fadein_dict.set_item("start_val", start_val)?;
+                                fadein_dict.set_item("end_val", end_val)?;
+                                effect_dict.set_item("FadeIn", fadein_dict)?;
+                            }
+                            ActionType::FadeOut(FadeOut {
+                                duration,
+                                apply_after,
+                                ..
+                            }) => {
+                                let fadeout_dict = PyDict::new_bound(py);
+                                fadeout_dict.set_item("duration", duration)?;
+                                fadeout_dict.set_item("apply_after", apply_after)?;
+                                effect_dict.set_item("FadeOut", fadeout_dict)?;
+                            }
+                            ActionType::ChangeSpeed(ChangeSpeed {
+                                duration,
+                                end_val,
+                                apply_after,
+                                ..
+                            }) => {
+                                let changespeed_dict = PyDict::new_bound(py);
+                                changespeed_dict.set_item("duration", duration)?;
+                                changespeed_dict.set_item("end_val", end_val)?;
+                                changespeed_dict.set_item("apply_after", apply_after)?;
+                                effect_dict.set_item("ChangeSpeed", changespeed_dict)?;
+                            }
+                            _ => {}
+                        }
+                        effects_list.append(effect_dict)?;
+                    }
+                    dict.set_item("effects", effects_list)?;
+                    return Ok(dict.into());
+                } else {
+                    return Ok(py.None());
+                }
+            }
+            Err(PyRuntimeError::new_err(
+                "Failed to acquire lock on currently_playing",
+            ))
+        })
+    }
 }
diff --git a/src/exceptions.rs b/src/exceptions.rs
new file mode 100644
index 0000000..6fe1684
--- /dev/null
+++ b/src/exceptions.rs
@@ -0,0 +1,18 @@
+use pyo3::{create_exception, PyErr};
+use pyo3::exceptions::PyException;
+use std::fmt;
+
+create_exception!(rpaudio, EffectConflictException, PyException);
+
+impl fmt::Display for EffectConflictException {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+      write!(f, "Value cannot be changed while effects are actively being applied.")
+  }
+}
+
+impl EffectConflictException {
+  pub fn with_context(context: &str) -> PyErr {
+      let message = format!("Cannot change property {} on AudioSink while scheduled effects are actively being applied.", context);
+      EffectConflictException::new_err(message)
+  }
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 158cdbb..ad84201 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,54 +1,73 @@
+use ::std::sync::mpsc::{Receiver, Sender};
 use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError};
 use pyo3::prelude::*;
 use pyo3::types::{PyDict, PyList};
 use rodio::{Decoder, OutputStream, Sink};
+use serde::Serialize;
 use std::collections::HashMap;
 use std::fs::File;
 use std::io::BufReader;
-use std::sync::{Arc, Mutex};
+use std::sync::{mpsc, Arc, Mutex, RwLock};
 use std::thread;
 use std::time::{Duration, Instant};
 use timesync::{ActionType, ChangeSpeed, EffectResult, EffectSync, FadeIn, FadeOut};
 mod audioqueue;
+mod exceptions;
 mod exmetadata;
 mod mixer;
 mod timesync;
+use crate::exceptions::EffectConflictException;
 pub use exmetadata::MetaData;
 unsafe impl Send for AudioSink {}
-use pyo3::types::PyModule;
-use std::sync::mpsc::{self, Receiver, Sender};
 
-#[derive(Clone)]
-#[pyclass]
-pub struct AudioSink {
-    is_playing: Arc<Mutex<bool>>,
-    callback: Arc<Mutex<Option<Py<PyAny>>>>,
-    cancel_callback: Arc<Mutex<bool>>,
-    sink: Option<Arc<Mutex<Sink>>>,
-    stream: Option<Arc<Mutex<OutputStream>>>,
-    pub metadata: MetaData,
-    volume: Arc<Mutex<f32>>,
-    start_time: Arc<Mutex<Option<Instant>>>,
-    position: Arc<Mutex<Duration>>,
-    pub action_sender: Option<Sender<ActionType>>,
-    pub action_receiver: Option<Arc<Mutex<Receiver<ActionType>>>>,
-    initial_play: bool,
-    effects: Arc<Mutex<Vec<Arc<EffectSync>>>>,
-    effects_chain: Arc<Mutex<Vec<ActionType>>>,
-    resume: bool,
+#[derive(Serialize)]
+struct AudioInfo {
+    position: f32,
+    speed: f32,
+    effects: Vec<String>,
+    volume: f32,
+    metadata: String,
+}
+
+#[pyfunction]
+fn get_audio_info(
+    audio_sink: &AudioSink,
+    metadata: String,
+    effects: Vec<String>,
+) -> PyResult<String> {
+    let sink = audio_sink.sink.as_ref().unwrap().lock().unwrap();
+
+    let start_time = audio_sink.start_time.as_ref();
+    let position = if let Some(start_time) = start_time {
+        start_time.elapsed().as_secs_f32()
+    } else {
+        0.0
+    };
+
+    let speed = 1.0;
+    let volume = sink.volume();
+    let info = AudioInfo {
+        position,
+        speed,
+        effects,
+        volume,
+        metadata,
+    };
+
+    let json_str = serde_json::to_string(&info).unwrap();
+    Ok(json_str)
 }
 
 impl AudioSink {
     pub fn handle_action_and_effects(&mut self, sink: Arc<Mutex<Sink>>) {
         if let Some(receiver) = &self.action_receiver {
-            if let Ok(action) = receiver.lock().unwrap().try_recv() {
+            if let Ok(action) = receiver.try_recv() {
                 let mut effects_guard = self.effects.lock().unwrap();
                 let effect_sync = Arc::new(EffectSync::new(
                     action.clone(),
                     sink.lock().unwrap().get_pos().as_secs_f32(),
                     Some(self.metadata.duration.unwrap() as f32),
                 ));
-
                 match action {
                     ActionType::FadeIn(fade_in) => {
                         if self.initial_play {
@@ -76,35 +95,47 @@ impl AudioSink {
                 let keep_effect = match effect.action {
                     ActionType::FadeIn(_fade_in) => match effect.update(current_position) {
                         EffectResult::Value(val) => {
+                            let mut lock = self.vol_manipulation_lock.write().unwrap();
                             sink.lock().unwrap().set_volume(val);
+                            *lock = true;
                             true
                         }
                         EffectResult::Ignored => true,
                         EffectResult::Completed(val) => {
-                            sink.lock().unwrap().set_volume(val); 
-                            false 
+                            let mut lock = self.vol_manipulation_lock.write().unwrap();
+                            sink.lock().unwrap().set_volume(val);
+                            *lock = false;
+                            false
                         }
                     },
                     ActionType::FadeOut(_fade_out) => match effect.update(current_position) {
                         EffectResult::Value(val) => {
+                            let mut lock = self.vol_manipulation_lock.write().unwrap();
                             sink.lock().unwrap().set_volume(val);
+                            *lock = true;
                             true
                         }
                         EffectResult::Ignored => true,
                         EffectResult::Completed(val) => {
+                            let mut lock = self.vol_manipulation_lock.write().unwrap();
                             sink.lock().unwrap().set_volume(val);
+                            *lock = false;
                             false
                         }
                     },
                     ActionType::ChangeSpeed(_change_speed) => {
                         match effect.update(current_position) {
                             EffectResult::Value(val) => {
+                                let mut lock = self.speed_manipulation_lock.write().unwrap();
                                 sink.lock().unwrap().set_speed(val);
+                                *lock = true;
                                 true
                             }
                             EffectResult::Ignored => true,
                             EffectResult::Completed(val) => {
                                 sink.lock().unwrap().set_speed(val);
+                                let mut lock = self.speed_manipulation_lock.write().unwrap();
+                                *lock = false;
                                 false
                             }
                         }
@@ -112,7 +143,6 @@ impl AudioSink {
                 };
 
                 if sink.lock().unwrap().is_paused() && self.resume {
-                    println!("Resuming audio effects");
                     sink.lock().unwrap().play();
                     self.resume = false;
                 }
@@ -122,6 +152,40 @@ impl AudioSink {
     }
 }
 
+#[derive(Clone)]
+#[pyclass]
+pub struct AudioSink {
+    pub is_playing: Arc<RwLock<bool>>,
+    callback: Arc<Option<Py<PyAny>>>,
+    cancel_callback: Arc<RwLock<bool>>,
+    sink: Option<Arc<Mutex<Sink>>>,
+    stream: Option<Arc<OutputStream>>,
+    pub metadata: MetaData,
+    volume: f32,
+    start_time: Option<Instant>,
+    position: Duration,
+    pub action_sender: Option<Sender<ActionType>>,
+    pub action_receiver: Option<Arc<Receiver<ActionType>>>,
+    initial_play: bool,
+    effects: Arc<Mutex<Vec<Arc<EffectSync>>>>,
+    effects_chain: Vec<ActionType>,
+    resume: bool,
+    vol_manipulation_lock: Arc<RwLock<bool>>,
+    speed_manipulation_lock: Arc<RwLock<bool>>,
+}
+
+impl AudioSink {
+    fn invoke_callback(callback: &Option<Py<PyAny>>) {
+        Python::with_gil(|py| {
+            if let Some(callback) = callback {
+                if let Err(e) = callback.call0(py) {
+                    eprintln!("Failed to invoke callback: {}", e);
+                }
+            }
+        });
+    }
+}
+
 #[pymethods]
 impl AudioSink {
     #[new]
@@ -129,21 +193,23 @@ impl AudioSink {
     pub fn new(callback: Option<Py<PyAny>>) -> Self {
         let (action_sender, action_receiver) = mpsc::channel();
         AudioSink {
-            is_playing: Arc::new(Mutex::new(false)),
-            callback: Arc::new(Mutex::new(callback)),
-            cancel_callback: Arc::new(Mutex::new(false)),
+            is_playing: Arc::new(RwLock::new(false)),
+            callback: Arc::new(callback),
+            cancel_callback: Arc::new(RwLock::new(false)),
             sink: None,
             stream: None,
             metadata: MetaData::default(),
-            volume: Arc::new(Mutex::new(1.0)),
-            start_time: Arc::new(Mutex::new(None)),
-            position: Mutex::new(Duration::from_secs(0)).into(),
+            volume: 1.0,
+            start_time: None,
+            position: Duration::from_secs(0),
             action_sender: Some(action_sender),
-            action_receiver: Some(Arc::new(Mutex::new(action_receiver))),
+            action_receiver: Some(Arc::new(action_receiver)),
             initial_play: true,
             effects: Arc::new(Mutex::new(Vec::new())),
-            effects_chain: Arc::new(Mutex::new(Vec::new())),
+            effects_chain: Vec::new(),
             resume: false,
+            vol_manipulation_lock: Arc::new(RwLock::new(false)),
+            speed_manipulation_lock: Arc::new(RwLock::new(false)),
         }
     }
 
@@ -167,6 +233,10 @@ impl AudioSink {
             "sample_rate",
             self.metadata.sample_rate.map(|rate| rate.to_string()),
         );
+        dict.insert("position", Some(self.get_pos()?.to_string()));
+        dict.insert("speed", Some(self.get_speed().to_string()));
+        dict.insert("volume", Some(self.get_volume()?.to_string()));
+
         dict.insert("channels", self.metadata.channels.clone());
         dict.insert(
             "duration",
@@ -175,6 +245,17 @@ impl AudioSink {
                 .map(|duration| format!("{:.1}", duration)),
         );
 
+        let effects_string = self
+            .effects
+            .lock()
+            .unwrap()
+            .iter()
+            .map(|effect| effect.to_string())
+            .collect::<Vec<String>>()
+            .join(", ");
+
+        dict.insert("effects", Some(effects_string));
+
         let py_dict = PyDict::new_bound(py);
 
         for (key, value) in dict {
@@ -186,12 +267,12 @@ impl AudioSink {
 
     #[getter]
     pub fn is_playing(&self) -> bool {
-        *self.is_playing.lock().unwrap()
+        *self.is_playing.read().unwrap()
     }
 
     #[getter]
     pub fn callback(&self) -> Option<Py<PyAny>> {
-        self.callback.lock().unwrap().clone()
+        (*self.callback).clone()
     }
 
     pub fn load_audio(&mut self, file_path: String) -> PyResult<Self> {
@@ -202,16 +283,9 @@ impl AudioSink {
         }
 
         let (new_stream, stream_handle) = OutputStream::try_default().unwrap();
-        let sink_result = Sink::try_new(&stream_handle);
-        let sink = match sink_result {
-            Ok(s) => Arc::new(Mutex::new(s)),
-            Err(e) => {
-                return Err(PyRuntimeError::new_err(format!(
-                    "Failed to create sink: {}",
-                    e
-                )))
-            }
-        };
+        let sink = Arc::new(Mutex::new(Sink::try_new(&stream_handle).map_err(|e| {
+            PyRuntimeError::new_err(format!("Failed to create sink: {}", e))
+        })?));
 
         let file_path_clone = file_path.clone();
         let file = File::open(file_path_clone).unwrap();
@@ -219,48 +293,52 @@ impl AudioSink {
             .map_err(|e| format!("Failed to decode audio file: {}", e))
             .unwrap();
 
-        let metadata = match exmetadata::extract_metadata(file_path.as_ref()) {
-            Ok(meta) => meta,
-            Err(_) => return Err(PyRuntimeError::new_err("Failed to extract metadata")),
-        };
-        self.metadata = metadata;
+        self.metadata = exmetadata::extract_metadata(std::path::Path::new(&file_path))
+            .map_err(|_| PyRuntimeError::new_err("Failed to extract metadata"))?;
 
-        sink.lock().unwrap().append(source);
+        {
+            sink.lock().unwrap().append(source);
+            sink.lock().unwrap().pause();
+        }
 
-        self.stream = Some(Arc::new(Mutex::new(new_stream)));
+        self.stream = Some(Arc::new(new_stream));
+        sink.lock().unwrap().set_volume(0.0);
         self.sink = Some(sink.clone());
 
         if let Some(sink) = &self.sink {
-            (*sink.lock().unwrap()).pause();
+            sink.lock().unwrap().pause();
         }
         let self_clone = self.clone();
 
-        let is_playing = self.is_playing.clone();
+        let is_playing_clone = Arc::clone(&self.is_playing);
         let callback = self.callback.clone();
-        let cancel_callback = self.cancel_callback.clone();
+        let cancel_callback_clone = self.cancel_callback.clone();
 
-        thread::spawn(move || {
-            let mut self_clone = self_clone;
+        thread::spawn({
+            let sink = Arc::clone(&sink);
 
-            loop {
-                {
-                    if self_clone.empty() {
-                        let mut is_playing_guard = is_playing.lock().unwrap();
-                        *is_playing_guard = false;
-                        drop(self_clone);
+            move || {
+                let mut self_clone = self_clone;
 
-                        let cancel_callback_guard = cancel_callback.lock().unwrap();
+                loop {
+                    let sink_guard = sink.lock().unwrap();
 
-                        if !*cancel_callback_guard {
-                            let callback_guard = callback.lock().unwrap();
-                            Self::invoke_callback(&*callback_guard);
+                    if sink_guard.empty() {
+                        let mut is_playing_guard = is_playing_clone.write().unwrap();
+                        *is_playing_guard = false;
+
+                        if !*cancel_callback_clone.read().unwrap() {
+                            Self::invoke_callback(&*callback);
                         }
+                        drop(self_clone);
                         break;
                     }
-                }
 
-                self_clone.handle_action_and_effects(sink.clone());
-                thread::sleep(Duration::from_millis(20));
+                    drop(sink_guard);
+                    self_clone.handle_action_and_effects(Arc::clone(&sink));
+
+                    thread::sleep(Duration::from_millis(100));
+                }
             }
         });
 
@@ -269,16 +347,14 @@ impl AudioSink {
 
     pub fn play(&mut self) -> PyResult<()> {
         if let Some(sink) = &self.sink {
-            *self.is_playing.lock().unwrap() = true;
+            *self.is_playing.write().unwrap() = true;
             if self.initial_play {
                 sink.lock().unwrap().play();
 
                 self.handle_action_and_effects(sink.clone());
             } else {
-                println!("Resuming audio in playu");
                 self.resume = true;
                 self.handle_action_and_effects(sink.clone());
-
             }
             Ok(())
         } else {
@@ -290,14 +366,9 @@ impl AudioSink {
 
     pub fn pause(&mut self) -> PyResult<()> {
         if let Some(sink) = &self.sink {
-            match sink.try_lock() {
-                Ok(sink) => {
-                    *self.is_playing.lock().unwrap() = false;
-                    sink.pause();
-                    Ok(())
-                }
-                Err(_) => Err(PyRuntimeError::new_err("Failed to acquire lock")),
-            }
+            *self.is_playing.write().unwrap() = false;
+            sink.lock().unwrap().pause();
+            Ok(())
         } else {
             Err(PyRuntimeError::new_err(
                 "No sink available. Load audio first.",
@@ -308,7 +379,7 @@ impl AudioSink {
     pub fn stop(&mut self) -> PyResult<()> {
         if let Some(sink) = &self.sink {
             sink.lock().unwrap().stop();
-            *self.is_playing.lock().unwrap() = false;
+            *self.is_playing.write().unwrap() = false;
 
             Ok(())
         } else {
@@ -318,28 +389,31 @@ impl AudioSink {
         }
     }
 
-    pub fn empty(&self) -> bool {
+    pub fn get_volume(&self) -> PyResult<f32> {
         if let Some(sink) = &self.sink {
-            sink.lock().unwrap().empty()
+            Ok(sink.lock().unwrap().volume())
         } else {
-            false
+            Err(PyRuntimeError::new_err(
+                "No sink available. Load audio first.",
+            ))
         }
     }
 
-    pub fn cancel_callback(&mut self) {
-        let mut cancel_guard = self.cancel_callback.lock().unwrap();
-        *cancel_guard = true;
-    }
-
     pub fn set_volume(&mut self, volume: f32) -> PyResult<()> {
         if volume < 0.0 || volume > 1.0 {
             return Err(PyValueError::new_err("Volume must be between 0.0 and 1.0."));
         }
 
         if let Some(sink) = &self.sink {
-            sink.lock().unwrap().set_volume(volume);
-            *self.volume.lock().unwrap() = volume;
-            Ok(())
+            let lock = self.vol_manipulation_lock.read().unwrap();
+
+            if *lock {
+                return Err(EffectConflictException::with_context("Volume"));
+            } else {
+                sink.lock().unwrap().set_volume(volume);
+                self.volume = volume;
+                Ok(())
+            }
         } else {
             Err(PyRuntimeError::new_err(
                 "No sink available to set volume. Load audio first.",
@@ -347,12 +421,11 @@ impl AudioSink {
         }
     }
 
-    pub fn get_volume(&self) -> PyResult<f32> {
+    pub fn get_pos(&self) -> PyResult<f64> {
         if let Some(sink) = &self.sink {
-            match sink.try_lock() {
-                Ok(sink) => Ok(sink.volume()),
-                Err(_) => Err(PyRuntimeError::new_err("Failed to acquire lock")),
-            }
+            let duration = sink.lock().unwrap().get_pos();
+            let position_seconds = duration.as_secs_f64();
+            Ok((position_seconds * 100.0).round() / 100.0)
         } else {
             Err(PyRuntimeError::new_err(
                 "No sink available. Load audio first.",
@@ -362,15 +435,50 @@ impl AudioSink {
 
     pub fn set_duration(&mut self, duration: f32) -> PyResult<()> {
         let duration = Duration::from_secs_f32(duration);
-        self.metadata.duration = Some(duration.as_secs_f64()); 
+        self.metadata.duration = Some(duration.as_secs_f64());
         Ok(())
     }
 
-    pub fn get_pos(&self) -> PyResult<f64> {
+    pub fn get_remaining_time(&self) -> PyResult<f64> {
         if let Some(sink) = &self.sink {
-            let duration = sink.lock().unwrap().get_pos();
-            let position_seconds = duration.as_secs_f64();
-            Ok((position_seconds * 100.0).round() / 100.0)
+            let sink_lock = sink.lock().unwrap();
+            let position = sink_lock.get_pos();
+
+            if let Some(duration) = self.metadata.duration {
+                let remaining = duration - position.as_secs_f64();
+                Ok((remaining * 100.0).round() / 100.0)
+            } else {
+                Err(PyRuntimeError::new_err("Audio duration is not available."))
+            }
+        } else {
+            Err(PyRuntimeError::new_err(
+                "No sink available. Load audio first.",
+            ))
+        }
+    }
+
+    pub fn get_speed(&self) -> f32 {
+        if let Some(sink) = &self.sink {
+            sink.lock().unwrap().speed()
+        } else {
+            1.0
+        }
+    }
+
+    pub fn set_speed(&mut self, speed: f32) -> PyResult<()> {
+        if speed <= 0.0 {
+            return Err(PyValueError::new_err("Speed must be greater than 0."));
+        }
+
+        if let Some(sink) = &self.sink {
+            let lock = self.speed_manipulation_lock.read().unwrap();
+
+            if *lock {
+                return Err(EffectConflictException::with_context("Speed"));
+            } else {
+                sink.lock().unwrap().set_speed(speed);
+                Ok(())
+            }
         } else {
             Err(PyRuntimeError::new_err(
                 "No sink available. Load audio first.",
@@ -389,9 +497,8 @@ impl AudioSink {
             let result = sink.lock().unwrap().try_seek(duration);
             match result {
                 Ok(_) => {
-                    *self.position.lock().unwrap() =
-                        Duration::from_secs_f64(self.get_pos().unwrap());
-                    *self.start_time.lock().unwrap() = Some(Instant::now());
+                    self.position = Duration::from_secs_f64(self.get_pos().unwrap());
+                    self.start_time = Some(Instant::now());
                     Ok(())
                 }
                 Err(e) => Err(PyRuntimeError::new_err(format!("Seek failed: {:?}", e))),
@@ -403,50 +510,29 @@ impl AudioSink {
         }
     }
 
-    pub fn get_speed(&self) -> f32 {
-        if let Some(sink) = &self.sink {
-            sink.lock().unwrap().speed()
-        } else {
-            1.0
-        }
+    pub fn cancel_callback(&mut self) {
+        let mut cancel_guard = self.cancel_callback.write().unwrap();
+        *cancel_guard = true;
     }
 
-    pub fn get_remaining_time(&self) -> PyResult<f64> {
+    pub fn empty(&self) -> bool {
         if let Some(sink) = &self.sink {
-            let sink_lock = sink.lock().unwrap();
-            let position = sink_lock.get_pos();
-
-            if let Some(duration) = self.metadata.duration {
-                let remaining = duration - position.as_secs_f64();
-                print!("remaining: {:?}", remaining);
-                Ok((remaining * 100.0).round() / 100.0)
-            } else {
-                Err(PyRuntimeError::new_err("Audio duration is not available."))
-            }
+            sink.lock().unwrap().empty()
         } else {
-            Err(PyRuntimeError::new_err(
-                "No sink available. Load audio first.",
-            ))
+            false
         }
     }
 
     pub fn apply_effects(&mut self, effect_list: Py<PyList>) -> PyResult<()> {
-        let _ = Python::with_gil(|py| {
-            let mut effects_guard = match self.effects_chain.lock() {
-                Ok(guard) => guard,
-                Err(_) => {
-                    return Err(PyRuntimeError::new_err(
-                        "Failed to acquire lock on effects_chain",
-                    ))
-                }
-            };
+        Python::with_gil(|py| -> PyResult<()> {
+            let effects_guard = &mut self.effects_chain;
 
             let _effect_list: Vec<Py<PyAny>> = effect_list.extract(py)?;
 
-            let rust_effect_list: Vec<ActionType> = _effect_list
+            let rust_effect_list: Result<Vec<ActionType>, PyErr> = _effect_list
                 .into_iter()
                 .map(|effect| {
-                    let effect = effect.downcast_bound(py).unwrap();
+                    let effect = effect.downcast_bound::<PyAny>(py).unwrap();
                     if let Ok(fade_in) = effect.extract::<FadeIn>() {
                         Ok(ActionType::FadeIn(fade_in))
                     } else if let Ok(fade_out) = effect.extract::<FadeOut>() {
@@ -457,46 +543,34 @@ impl AudioSink {
                         Err(PyTypeError::new_err("Unknown effect type"))
                     }
                 })
-                .collect::<Result<Vec<ActionType>, PyErr>>()?;
+                .collect();
 
-            *effects_guard = rust_effect_list;
+            *effects_guard = rust_effect_list?;
 
             Ok(())
-        });
-        let effects_guard = match self.effects_chain.lock() {
-            Ok(guard) => guard,
-            Err(_) => {
-                eprintln!("Failed to acquire lock on effects_chain");
-                return Err(PyRuntimeError::new_err(
-                    "Failed to acquire lock on effects_chain",
-                ));
-            }
-        };
+        })?;
+
         if let Some(sender) = self.action_sender.take() {
+            let effects_guard = &self.effects_chain;
+
             for effect in effects_guard.iter() {
-                sender.send(effect.clone()).unwrap();
+                sender
+                    .send(effect.clone())
+                    .map_err(|_| {
+                        eprintln!("Failed to send effect");
+                    })
+                    .ok();
             }
         } else {
             eprintln!("Action sender is None");
         }
-        Ok(())
-    }
-}
 
-impl AudioSink {
-    fn invoke_callback(callback: &Option<Py<PyAny>>) {
-        Python::with_gil(|py| {
-            if let Some(callback) = callback {
-                if let Err(e) = callback.call0(py) {
-                    eprintln!("Failed to invoke callback: {}", e);
-                }
-            }
-        });
+        Ok(())
     }
 }
 
 #[pymodule]
-fn rpaudio(m: &Bound<'_, PyModule>) -> PyResult<()> {
+fn rpaudio(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
     m.add_class::<AudioSink>()?;
     m.add_class::<mixer::ChannelManager>()?;
     m.add_class::<audioqueue::AudioChannel>()?;
@@ -504,6 +578,7 @@ fn rpaudio(m: &Bound<'_, PyModule>) -> PyResult<()> {
     m.add_class::<FadeIn>()?;
     m.add_class::<FadeOut>()?;
     m.add_class::<ChangeSpeed>()?;
-
+    m.add_function(wrap_pyfunction!(get_audio_info, m)?)?;
+    m.add("EffectConflictException", py.get_type_bound::<EffectConflictException>())?;
     Ok(())
 }
diff --git a/src/timesync.rs b/src/timesync.rs
index 01c090b..5dfbb50 100644
--- a/src/timesync.rs
+++ b/src/timesync.rs
@@ -1,3 +1,5 @@
+use std::fmt;
+
 use pyo3::prelude::*;
 
 #[derive(Clone, Debug, Copy, PartialEq)]
@@ -98,6 +100,27 @@ impl ChangeSpeed {
     }
 }
 
+impl fmt::Display for FadeIn {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "FadeIn {{ duration: {:?}, start_val: {:?}, end_val: {:?} apply_after: {:?} }}", 
+            self.duration, self.start_val, self.end_val, self.apply_after)
+    }
+}
+
+impl fmt::Display for FadeOut {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "FadeOut {{ duration: {:?}, start_val: {:?}, end_val: {:?} apply_after: {:?} }}", 
+            self.duration, self.start_val, self.end_val, self.apply_after)
+    }
+}
+
+impl fmt::Display for ChangeSpeed {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "ChangeSpeed {{ duration: {:?}, start_val: {:?}, end_val: {:?} apply_after: {:?} }}", 
+            self.duration, self.start_val, self.end_val, self.apply_after)
+    }
+}
+
 #[derive(Clone, Debug, PartialEq)]
 #[allow(non_upper_case_globals)]
 #[pyclass]
@@ -119,12 +142,38 @@ pub struct EffectSync {
     pub action: ActionType,
 }
 
+impl fmt::Display for EffectSync {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "EffectSync {{ start_position: {}, duration: {}, start_val: {}, end_val: {}, completion_pos: {}, current_position: {}, apply_after: {:?}, action: {} }}", 
+               self.start_position, 
+               self.duration, 
+               self.start_val, 
+               self.end_val, 
+               self.completion_pos, 
+               self.current_position, 
+               self.apply_after, 
+               self.action)
+    }
+}
+
 pub enum EffectResult {
     Value(f32),
     Ignored,
     Completed(f32),
 }
 
+impl fmt::Display for ActionType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ActionType::FadeIn(_) => write!(f, "FadeIn"),
+
+            ActionType::FadeOut(_) => write!(f, "FadeOut"),
+
+            ActionType::ChangeSpeed(_) => write!(f, "ChangeSpeed"),
+        }
+    }
+}
+
 impl EffectSync {
     pub fn new(action: ActionType, current_position: f32, sink_duration: Option<f32>) -> Self {
         let (start_position, duration, start_val, end_val, apply_after) = match action {
@@ -203,11 +252,13 @@ impl EffectSync {
             return EffectResult::Ignored;
         } else {
             if current_position >= self.completion_pos {
-                return EffectResult::Completed(self.end_val);
+                let rounded_end_val = format!("{:.2}", self.end_val).parse::<f32>().unwrap_or(self.end_val);
+                return EffectResult::Completed(rounded_end_val);
             } else {
                 let progress = (current_position - self.start_position)
                     / (self.completion_pos - self.start_position);
-                let progress = progress.clamp(0.0, 1.0);
+                let rounded_progresss = format!("{:.2}", progress).parse::<f32>().unwrap_or(progress);
+                let progress = rounded_progresss.clamp(0.0, 1.0);
                 let set_val = self.start_val + (self.end_val - self.start_val) * progress;
                 return EffectResult::Value(set_val);
             }


Test patch:
diff --git a/pytest_output.txt b/pytest_output.txt
index 2f719e9..a833938 100644
--- a/pytest_output.txt
+++ b/pytest_output.txt
@@ -5,63 +5,65 @@ rootdir: C:\Users\16145\Desktop\code_24\frpaudio\rpaudio
 configfile: pyproject.toml
 plugins: anyio-4.6.0, asyncio-0.24.0
 asyncio: mode=Mode.STRICT, default_loop_scope=None
-collecting ... collected 54 items
+collecting ... collected 56 items
 
 tests/test_AudioSink.py::test_fade_in PASSED                             [  1%]
 tests/test_AudioSink.py::test_fade_out PASSED                            [  3%]
 tests/test_AudioSink.py::test_change_speed PASSED                        [  5%]
 tests/test_AudioSink.py::test_effects_applied_over_time PASSED           [  7%]
-tests/test_AudioSink.py::test_effect_completion PASSED                   [  9%]
-tests/test_AudioSink.py::test_set_duration PASSED                        [ 11%]
-tests/test_AudioSink.py::test_set_volume_valid_input PASSED              [ 12%]
-tests/test_AudioSink.py::test_set_volume_out_of_range PASSED             [ 14%]
-tests/test_AudioSink.py::test_volume_uninitialized_sink PASSED           [ 16%]
-tests/test_AudioSink.py::test_get_volume_returns_correct_value PASSED    [ 18%]
-tests/test_AudioSink.py::test_cancel_callback PASSED                     [ 20%]
-tests/test_AudioSink.py::test_stop_uninitialized_sink PASSED             [ 22%]
-tests/test_AudioSink.py::test_pause_uninitialized_sink PASSED            [ 24%]
-tests/test_AudioSink.py::test_no_file_provided PASSED                    [ 25%]
-tests/test_AudioSink.py::test_audio_handler_no_callback PASSED           [ 27%]
-tests/test_AudioSink.py::test_load_audio_multiple_times PASSED           [ 29%]
-tests/test_AudioSink.py::test_audio_handler_callback PASSED              [ 31%]
-tests/test_AudioSink.py::test_default_values PASSED                      [ 33%]
-tests/test_AudioSink.py::test_play_audio PASSED                          [ 35%]
-tests/test_AudioSink.py::test_pause PASSED                               [ 37%]
-tests/test_AudioSink.py::test_resume PASSED                              [ 38%]
-tests/test_AudioSink.py::test_set_volume PASSED                          [ 40%]
-tests/test_AudioSink.py::test_try_seek PASSED                            [ 42%]
-tests/test_AudioSink.py::test_get_pos PASSED                             [ 44%]
-tests/test_AudioSink.py::test_set_speed PASSED                           [ 46%]
-tests/test_AudioSink.py::test_get_speed PASSED                           [ 48%]
-tests/test_AudioSink.py::test_stop PASSED                                [ 50%]
-tests/test_AudioSink.py::test_metadata PASSED                            [ 51%]
-tests/test_AudioSink.py::test_callback_called PASSED                     [ 53%]
-tests/test_AudioSink_metadata.py::test_metadata_wav PASSED               [ 55%]
-tests/test_AudioSink_metadata.py::test_metadata_mp3 PASSED               [ 57%]
-tests/test_AudioSink_metadata.py::test_metadata_flac PASSED              [ 59%]
-tests/test_ChannelManager.py::test_add_channel PASSED                    [ 61%]
-tests/test_ChannelManager.py::test_channel_retrieval PASSED              [ 62%]
-tests/test_ChannelManager.py::test_channel_retrieval_not_found PASSED    [ 64%]
-tests/test_ChannelManager.py::test_start_all PASSED                      [ 66%]
-tests/test_ChannelManager.py::test_stop_all PASSED                       [ 68%]
-tests/test_ChannelManager.py::test_drop_channel PASSED                   [ 70%]
-tests/test_ChannelManager.py::test_drop_channel_not_found PASSED         [ 72%]
-tests/test_Channel_multi_sink.py::test_audio_channel_effects_chain PASSED [ 74%]
-tests/test_Channel_multi_sink.py::test_audio_channel_auto_consume PASSED [ 75%]
-tests/test_Channel_multi_sink.py::test_audiochannel_multiple_sink_pushes PASSED [ 77%]
-tests/test_Channel_multi_sink.py::test_drop_current_audio PASSED         [ 79%]
-tests/test_Channel_multi_sink.py::test_current_audio PASSED              [ 81%]
+tests/test_AudioSink.py::test_effects_applied_over_time_vol_exception PASSED [  8%]
+tests/test_AudioSink.py::test_effects_applied_over_time_speed_exception PASSED [ 10%]
+tests/test_AudioSink.py::test_effect_completion PASSED                   [ 12%]
+tests/test_AudioSink.py::test_set_duration PASSED                        [ 14%]
+tests/test_AudioSink.py::test_set_volume_valid_input PASSED              [ 16%]
+tests/test_AudioSink.py::test_set_volume_out_of_range PASSED             [ 17%]
+tests/test_AudioSink.py::test_volume_uninitialized_sink PASSED           [ 19%]
+tests/test_AudioSink.py::test_get_volume_returns_correct_value PASSED    [ 21%]
+tests/test_AudioSink.py::test_cancel_callback PASSED                     [ 23%]
+tests/test_AudioSink.py::test_stop_uninitialized_sink PASSED             [ 25%]
+tests/test_AudioSink.py::test_pause_uninitialized_sink PASSED            [ 26%]
+tests/test_AudioSink.py::test_no_file_provided PASSED                    [ 28%]
+tests/test_AudioSink.py::test_audio_handler_no_callback PASSED           [ 30%]
+tests/test_AudioSink.py::test_load_audio_multiple_times PASSED           [ 32%]
+tests/test_AudioSink.py::test_audio_handler_callback PASSED              [ 33%]
+tests/test_AudioSink.py::test_default_values PASSED                      [ 35%]
+tests/test_AudioSink.py::test_play_audio PASSED                          [ 37%]
+tests/test_AudioSink.py::test_pause PASSED                               [ 39%]
+tests/test_AudioSink.py::test_resume PASSED                              [ 41%]
+tests/test_AudioSink.py::test_set_volume PASSED                          [ 42%]
+tests/test_AudioSink.py::test_try_seek PASSED                            [ 44%]
+tests/test_AudioSink.py::test_get_pos PASSED                             [ 46%]
+tests/test_AudioSink.py::test_set_speed PASSED                           [ 48%]
+tests/test_AudioSink.py::test_get_speed PASSED                           [ 50%]
+tests/test_AudioSink.py::test_stop PASSED                                [ 51%]
+tests/test_AudioSink.py::test_metadata PASSED                            [ 53%]
+tests/test_AudioSink.py::test_callback_called PASSED                     [ 55%]
+tests/test_AudioSink_metadata.py::test_metadata_wav PASSED               [ 57%]
+tests/test_AudioSink_metadata.py::test_metadata_mp3 PASSED               [ 58%]
+tests/test_AudioSink_metadata.py::test_metadata_flac PASSED              [ 60%]
+tests/test_ChannelManager.py::test_add_channel PASSED                    [ 62%]
+tests/test_ChannelManager.py::test_channel_retrieval PASSED              [ 64%]
+tests/test_ChannelManager.py::test_channel_retrieval_not_found PASSED    [ 66%]
+tests/test_ChannelManager.py::test_start_all PASSED                      [ 67%]
+tests/test_ChannelManager.py::test_stop_all PASSED                       [ 69%]
+tests/test_ChannelManager.py::test_drop_channel PASSED                   [ 71%]
+tests/test_ChannelManager.py::test_drop_channel_not_found PASSED         [ 73%]
+tests/test_Channel_multi_sink.py::test_audio_channel_effects_chain PASSED [ 75%]
+tests/test_Channel_multi_sink.py::test_audio_channel_auto_consume PASSED [ 76%]
+tests/test_Channel_multi_sink.py::test_audiochannel_multiple_sink_pushes PASSED [ 78%]
+tests/test_Channel_multi_sink.py::test_drop_current_audio PASSED         [ 80%]
+tests/test_Channel_multi_sink.py::test_current_audio PASSED              [ 82%]
 tests/test_Channel_multi_sink.py::test_autoplay_songs PASSED             [ 83%]
 tests/test_Channel_single_sink.py::test_play_audio PASSED                [ 85%]
 tests/test_Channel_single_sink.py::test_pause PASSED                     [ 87%]
-tests/test_Channel_single_sink.py::test_resume PASSED                    [ 88%]
-tests/test_Channel_single_sink.py::test_set_volume PASSED                [ 90%]
+tests/test_Channel_single_sink.py::test_resume PASSED                    [ 89%]
+tests/test_Channel_single_sink.py::test_set_volume PASSED                [ 91%]
 tests/test_Channel_single_sink.py::test_try_seek PASSED                  [ 92%]
 tests/test_Channel_single_sink.py::test_get_pos PASSED                   [ 94%]
 tests/test_Channel_single_sink.py::test_get_speed PASSED                 [ 96%]
 tests/test_Channel_single_sink.py::test_stop PASSED                      [ 98%]
 tests/test_Channel_single_sink.py::test_callbacks_called PASSED          [100%]
- 1727368661 - All tests completed.
+ 1727870459 - All tests completed.
 
 
-============================= 54 passed in 16.06s =============================
+============================= 56 passed in 16.52s =============================
diff --git a/tests/test_AudioSink.py b/tests/test_AudioSink.py
index b3891e9..3f9377f 100644
--- a/tests/test_AudioSink.py
+++ b/tests/test_AudioSink.py
@@ -28,7 +28,6 @@ async def test_fade_in(audio_handler):
     handler.stop()
 
 
-
 @pytest.mark.asyncio
 async def test_fade_out(audio_handler):
     handler, _ = audio_handler
@@ -36,6 +35,7 @@ async def test_fade_out(audio_handler):
         duration=1.0, apply_after=handler.get_pos())
     handler.apply_effects([fade_out_effect])
     handler.play()
+    handler.set_volume(1.0)
     initial_volume = handler.get_volume()
     await asyncio.sleep(1)
     new_volume = handler.get_volume()
@@ -65,15 +65,42 @@ async def test_effects_applied_over_time(audio_handler):
     await asyncio.sleep(1)
     halfway_volume = handler.get_volume()
     assert halfway_volume >= 0.5 and halfway_volume <= (
-        1.0 )
+        1.0)
+    handler.stop()
+
+
+@pytest.mark.asyncio
+async def test_effects_applied_over_time_vol_exception(audio_handler):
+    handler, _ = audio_handler
+    fade_in_effect = rpaudio.FadeIn(start_val=0.0, end_val=1.0, duration=1.0)
+    handler.apply_effects([fade_in_effect])
+    handler.play()
+    await asyncio.sleep(0.1)
+
+    with pytest.raises(rpaudio.EffectConflictException) as exc_info:
+        handler.set_volume(0.0)
+
     handler.stop()
 
 
+@pytest.mark.asyncio
+async def test_effects_applied_over_time_speed_exception(audio_handler):
+    handler, _ = audio_handler
+    speed_effect = rpaudio.ChangeSpeed(end_val=1.5, duration=1.0)
+    handler.apply_effects([speed_effect])
+    handler.play()
+    await asyncio.sleep(0.1)
+
+    with pytest.raises(rpaudio.EffectConflictException) as exc_info:
+        handler.set_speed(0.9)
+
+    handler.stop()
+
 @pytest.mark.asyncio
 async def test_effect_completion(audio_handler):
     handler, _ = audio_handler
     fade_out_effect = rpaudio.FadeOut(apply_after=handler.get_pos(),
-        duration=1.0)
+                                      duration=1.0)
     handler.apply_effects([fade_out_effect])
     handler.play()
     await asyncio.sleep(1.2)
@@ -194,7 +221,7 @@ async def test_audio_handler_callback():
 async def test_default_values(audio_handler):
     handler, _ = audio_handler
     assert handler.is_playing is False
-    assert handler.get_volume() == 1.0
+    assert handler.get_volume() == 0.0
     assert handler.get_speed() == 1.0
 
 
@@ -218,7 +245,6 @@ async def test_pause(audio_handler):
     handler.stop()
 
 
-
 @pytest.mark.asyncio
 async def test_resume(audio_handler):
     handler, _ = audio_handler
@@ -267,6 +293,7 @@ async def test_get_pos(audio_handler):
 @pytest.mark.asyncio
 async def test_set_speed(audio_handler):
     handler, _ = audio_handler
+    handler.set_speed(1.5)
     handler.play()
     await asyncio.sleep(0.1)
     speed_up = rpaudio.ChangeSpeed(end_val=1.5)
diff --git a/tests/test_Channel_multi_sink.py b/tests/test_Channel_multi_sink.py
index ad6f670..aae2f76 100644
--- a/tests/test_Channel_multi_sink.py
+++ b/tests/test_Channel_multi_sink.py
@@ -98,6 +98,7 @@ async def test_audio_channel_auto_consume(audio_callback):
     channel_1.current_audio.stop()
     await asyncio.sleep(0.1)
     audio_callback.assert_called_once()
+    await asyncio.sleep(0.1)
     assert len(channel_1.queue_contents) == 0
 
 
diff --git a/tests/test_Channel_single_sink.py b/tests/test_Channel_single_sink.py
index 8bb0513..e8271a1 100644
--- a/tests/test_Channel_single_sink.py
+++ b/tests/test_Channel_single_sink.py
@@ -125,7 +125,7 @@ async def test_callbacks_called(audio_channel):
 
     channel.current_audio.stop()
 
-    await asyncio.sleep(0.2)
+    await asyncio.sleep(0.1)
 
     mock_callback_1.assert_called_once()
     mock_callback_2.assert_not_called()