Skip to content
This repository has been archived by the owner on Oct 25, 2021. It is now read-only.

Controllable audio system #81

Open
QbieShay opened this issue Jun 16, 2019 · 2 comments
Open

Controllable audio system #81

QbieShay opened this issue Jun 16, 2019 · 2 comments

Comments

@QbieShay
Copy link

At the moment, the system used by evoli to play audio is amethyst's AudioBundle/DjSystem.

Changing the music that is being played is currently not possible, a new, controllable system is required.

I'm not entirely sure what would be a good implementation for this.

Related to #76 ( and what's currently blocking me on #80 :< )

Sorry in advance if i mix up some concepts, I'm new!

@azriel91
Copy link
Member

This should help:

  1. Have a system (let's call it BgmSystem) which subscribes to day / night cycle change events.

    #[derive(Debug)]
    pub enum DayCycleEvent {
        DayBegin,
        NightBegin,
    }
    
    type BgmSystemData<'s> = (
        Read<'s, EventChannel<DayCycleEvent>>,
        // ..
    );
  2. The BgmSystem needs to be able to play sounds.

    Because we need to stop the sound when the day/night cycle changes, we need to hold onto a Sink:

    use std::io::Cursor;
    
    use rodio;
    
    let device = rodio::default_output_device().expect("No default output device");
    let source = music.musics.get("day/night soundtrack").expect("not found").clone();
    let reader = Cursor::new(source);
    let sink = rodio::play_once(device, reader);
    
    // On change events, we can go:
    sink.stop();

    Note: Amethyst's amethyst::audio::Output.play_once(..) does not return you the Sink, so you can't use that as is -- you can if this is changed, I think it's a good idea.

  3. We need to stop the sound on an event, so we'll insert it into the world.

    type BgmSystemData<'s> = (
        Read<'s, EventChannel<DayCycleEvent>>,
        Read<'s, Music>,
        // Inserted by `AudioSystem.setup()`, so you need that in your dispatcher.
        Read<'s, Option<Device>>,
        Write<'s, Option<Sink>>,
    );
    
    impl System<'s> for BgmSystem {
        type SystemData = BgmSystemData<'s>;
    
        fn run(&mut self, (channel, music, device, sink_resource): Self::SystemData) {
            // On event:
            // Play soundtrack.
            let source = music.musics.get("day/night soundtrack").expect("not found").clone();
            let reader = Cursor::new(source);
            let sink = rodio::play_once(device, reader);
    
            *sink_resource = Some(sink);
        }
    }
  4. rodio's Sink type does not allow you to re-use a sink after invoking sink.stop(). So to switch audio, we need a new Sink every time.

    impl System<'s> for BgmSystem {
        type SystemData = BgmSystemData<'s>;
    
        fn run(&mut self, (channel, music, device, sink_resource): Self::SystemData) {
            // On event:
            // Stop previous soundtrack if any.
            if let Some(sink) = *sink_resource {
                sink.stop();
            }
    
            // Play soundtrack.
            // ..
        }
    }
  5. Listen to events:

    #[derive(Debug, Default)]
    pub struct BgmSystem {
        /// Reader ID for the `DayCycleEvent` event channel.
        day_cycle_event_rid: Option<ReaderId<DayCycleEvent>>,
    }
    
    impl System<'s> for BgmSystem {
        type SystemData = BgmSystemData<'s>;
    
        fn run(&mut self, (channel, music, device, sink_resource): Self::SystemData) {
            channel
                .read(
                    self.day_cycle_event_rid
                        .as_mut()
                        .expect("Expected reader ID to exist for HitDetectionSystem."),
                )
                .for_each(|ev| {
                    // Stop previous soundtrack if any.
                    if let Some(sink) = *sink_resource {
                        sink.stop();
                    }
    
                    // Play soundtrack.
                    let source = match ev {
                        DayCycleEvent::DayBegin => "day.wav",
                        DayCycleEvent::NightBegin => "night.wav",
                    }
                    let source = music.musics.get().expect("soundtrack not loaded.").clone();
                    // ..
                });
        }
    
        fn setup(&mut self, res: &mut Resources) {
            Self::SystemData::setup(res);
            self.day_cycle_event_rid = Some(
                res.fetch_mut::<EventChannel<DayCycleEvent>>()
                    .register_reader(),
            );
        }
    }
  6. When the day / night cycle changes, send a new DayCycleEvent.

    // In `DayNightCycle`, when night time happens:
    day_cycle_event_channel.write(DayCycleEvent::NightBegin);
  7. When the game starts, we need to send the first event to start the first soundtrack.

    // In `SomethingState.on_start()`
    day_cycle_event_channel.write(DayCycleEvent::DayBegin);

@QbieShay
Copy link
Author

Wow that's a lot of information! Thank you so much! I'll try to process and understand all :) in the meantime, if this becomes urgent, please feel free to implement it, I don't want to hold back anyone

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants