Compare commits

..

5 commits

Author SHA1 Message Date
Silvio Brändle
ffb34753ce
Add design pattern 2025-01-27 23:59:42 +01:00
Cédric Ackermann
594a530852
Update actual structure 2025-01-27 18:20:41 +01:00
533bc57232
update README.md 2025-01-27 14:37:32 +01:00
Silvio Braendle
409bcfb768
Improve file pick label 2025-01-27 13:39:51 +01:00
Silvio Brändle
ccb46ce1ea
Merge pull request #2 from silvio2402/addComments
add some comments
2025-01-27 12:36:25 +01:00
6 changed files with 47 additions and 62 deletions

View file

@ -1,13 +1,26 @@
# syncaud
## Requirements
- [At least one custom exception class that extends Exception should be written.](./src/client_handler.rs#L6) (The struct `BindError` is a custom exception, which defines the error type for binding errors.)
- [At least one inheritance implementation must be included.](./src/client_handler.rs#L37) (The `NetworkListener` implements the `ClientHandler` and `Listener` trait.)
- [At least one custom (non-Spring Boot interface) interface should be created and used.](./src/client_handler.rs#14) (The `ClientHandler` trait is like an interface in Rust.)
## Design Pattern
In our code we adhered to the Strategy Design Pattern as described [here](https://rust-unofficial.github.io/patterns/patterns/behavioural/strategy.html). For example, the ClientHandler trait acts as the strategy interface and the NetworkListener implements the strategy via the trait. This means that in the future, this strategy could be replaced to allow for a different communication medium, e.g. Serial.
## Featurelist
- The user interface should be interactive and usable by any person.
- Sounds are played synchronously across multiple devices.
- Any mp3 file can be uploaded by the user to play.
## Planned structure
![syncaud(1)](https://github.com/user-attachments/assets/264a3af8-717f-43ed-9c51-2d1da54b8c8a)
## Actual structure
!![Syncaud](https://github.com/user-attachments/assets/97cc57de-a53d-48e3-b09e-8057bba05bac)
![Syncaud(2)](https://github.com/user-attachments/assets/0b610dec-5589-40e5-82c3-34384649bd61)

View file

@ -41,11 +41,10 @@ impl Listener for NetworkListener {
fn listen(port: u16) -> Result<()> {
// Create the bind address string
let bind_addr = format!("0.0.0.0:{}", port);
// Attempt to bind the UDP socket to the address
let socket = UdpSocket::bind(bind_addr);
let socket = match socket {
Ok(s) => s,
Ok(s) => s, // If successful, use the socket
Err(e) => {
// If there's an error, return a BindError
return Err(BindError {
@ -61,19 +60,14 @@ impl Listener for NetworkListener {
loop {
// Buffer to store incoming data
let mut data = [0; 1_048_576];
// Receive data from the socket
let (amt, src) = socket.recv_from(&mut data).expect("Didn't receive data");
// Print the amount of data received and the source address
println!("Received {} bytes from {}", amt, src);
// Slice the buffer to the actual amount of data received
let data = &mut data[..amt];
// Deserialize the data into a Message
let msg = bincode::deserialize::<Message>(data).expect("Failed to deserialize");
// Handle the deserialized message
NetworkListener::handle_message(msg);
}

View file

@ -20,10 +20,8 @@ pub struct NetworkDispatcher;
impl NetworkDispatcher {
// Load a sound file and return a Decoder
fn load_sound(path: String) -> Decoder<BufReader<File>> {
// Open the file
let file = BufReader::new(File::open(path).unwrap());
// Decode the file
Decoder::new(file).unwrap()
let file = BufReader::new(File::open(path).unwrap()); // Open the file
Decoder::new(file).unwrap() // Decode the file
}
}
@ -41,28 +39,23 @@ impl Dispatcher for NetworkDispatcher {
let channels = source.channels();
let sample_rate = source.sample_rate();
let duration = source.total_duration().unwrap();
// Length of each chunk to send
let chunk_len = Duration::from_millis(200);
let chunk_len = Duration::from_millis(200); // Length of each chunk to send
// Initial offset
let mut offset = Duration::from_secs(0);
let mut offset = Duration::from_secs(0); // Initial offset
// Iterate over each address
for addr in addrs {
// Skip empty addresses
if addr.is_empty() {
continue;
continue; // Skip empty addresses
}
// Clone the source for each thread
let source = source.clone();
let source = source.clone(); // Clone the source for each thread
// Spawn a new thread for each address
thread::spawn(move || {
// Bind to a local UDP socket
let sock = UdpSocket::bind("0.0.0.0:0").expect("Failed to bind");
// Connect to the address
sock.connect(addr.clone()).expect("Failed to connect");
sock.connect(addr.clone()).expect("Failed to connect"); // Connect to the address
// Loop until the entire duration is covered
while offset < duration {
@ -73,13 +66,11 @@ impl Dispatcher for NetworkDispatcher {
.take_duration(chunk_len)
.collect::<Vec<i16>>();
// Update the offset
offset += chunk_len;
offset += chunk_len; // Update the offset
// Create a PlaySound message
let msg = Message::PlaySound(PlaySound {
// Timestamp for synchronization
timestamp: (now + offset).as_micros(),
timestamp: (now + offset).as_micros(), // Timestamp for synchronization
channels: channels,
sample_rate: sample_rate,
sound_data: sound_data,

View file

@ -1,5 +1,4 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// Hide console window on Windows in release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // Hide console window on Windows in release mode
// Import necessary modules and crates
use client_handler::{Listener, NetworkListener};
@ -19,8 +18,7 @@ mod ui_app;
fn main() -> eframe::Result {
// Define options for the eframe application
let options = eframe::NativeOptions {
// Set initial window size
viewport: egui::ViewportBuilder::default().with_inner_size([700.0, 400.0]),
viewport: egui::ViewportBuilder::default().with_inner_size([700.0, 400.0]), // Set initial window size
..Default::default()
};
@ -31,15 +29,13 @@ fn main() -> eframe::Result {
match result {
Ok(_) => {}
Err(e) => {
// Print error message if binding fails
eprintln!("Failed to bind: {}", e.message);
// Try with port 3001 if port 3000 fails
eprintln!("Failed to bind: {}", e.message); // Print error message if binding fails
// Try with port 3001 if port 3000 fails
let result = NetworkListener::listen(3001);
match result {
Ok(_) => {}
Err(e) => {
// Print error message if binding fails again
eprintln!("Failed to bind again: {}", e.message);
eprintln!("Failed to bind again: {}", e.message); // Print error message if binding fails again
}
}
}
@ -48,17 +44,13 @@ fn main() -> eframe::Result {
// Run the eframe application
eframe::run_native(
// Application title
"My egui App",
// Application options
options,
"My egui App", // Application title
options, // Application options
Box::new(|cc| {
// This gives us image support:
// Install image loaders for egui context
egui_extras::install_image_loaders(&cc.egui_ctx);
egui_extras::install_image_loaders(&cc.egui_ctx); // Install image loaders for egui context
// Initialize the UI application
Ok(Box::<UIApp>::default())
Ok(Box::<UIApp>::default()) // Initialize the UI application
}),
)
}

View file

@ -1,25 +1,16 @@
use serde::{Deserialize, Serialize};
// Importing necessary traits from serde for serialization and deserialization
use serde::{Deserialize, Serialize}; // Importing necessary traits from serde for serialization and deserialization
// Define an enum called Message which can hold different types of messages
#[derive(Serialize, Deserialize, Debug)]
// Automatically generate code for serialization, deserialization, and debugging
#[derive(Serialize, Deserialize, Debug)] // Automatically generate code for serialization, deserialization, and debugging
pub enum Message {
PlaySound(PlaySound),
// A variant of the Message enum that holds a PlaySound struct
PlaySound(PlaySound), // A variant of the Message enum that holds a PlaySound struct
}
// Define a struct called PlaySound which represents a sound to be played
#[derive(Serialize, Deserialize, Debug)]
// Automatically generate code for serialization, deserialization, and debugging
#[derive(Serialize, Deserialize, Debug)] // Automatically generate code for serialization, deserialization, and debugging
pub struct PlaySound {
pub timestamp: u128,
// The timestamp when the sound should be played
pub channels: u16,
// The number of audio channels (e.g., 2 for stereo)
pub sample_rate: u32,
// The sample rate of the audio (e.g., 44100 for 44.1 kHz)
pub sound_data: Vec<i16>,
// The actual sound data as a vector of 16-bit integers
pub timestamp: u128, // The timestamp when the sound should be played
pub channels: u16, // The number of audio channels (e.g., 2 for stereo)
pub sample_rate: u32, // The sample rate of the audio (e.g., 44100 for 44.1 kHz)
pub sound_data: Vec<i16>, // The actual sound data as a vector of 16-bit integers
}

View file

@ -77,7 +77,11 @@ impl eframe::App for UIApp {
}
// Displaying the picked file path
ui.label(format!("Picked file: {:?}", self.picked_file));
let picked_file_label = match &self.picked_file {
Some(path) => format!("Picked file: {:?}", path),
None => "No file picked".to_owned(),
};
ui.label(picked_file_label);
ui.add_space(25.0); // Adding space in the UI
ui.separator(); // Adding a separator line