diff --git a/Resources/EnginePrototypes/Audio/audio_entities.yml b/Resources/EnginePrototypes/Audio/audio_entities.yml new file mode 100644 index 000000000..0013afe62 --- /dev/null +++ b/Resources/EnginePrototypes/Audio/audio_entities.yml @@ -0,0 +1,5 @@ +- type: entity + id: Audio + name: Audio + description: Audio entity used by engine + save: false \ No newline at end of file diff --git a/Resources/EnginePrototypes/Audio/audio_presets.yml b/Resources/EnginePrototypes/Audio/audio_presets.yml new file mode 100644 index 000000000..d13767f02 --- /dev/null +++ b/Resources/EnginePrototypes/Audio/audio_presets.yml @@ -0,0 +1,3076 @@ +- type: audioPreset + id: Generic + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.8913 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.8300 + decayLfRatio: 1.0000 + reflectionsGain: 0.0500 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: PaddedCell + density: 0.1715 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0010 + gainLf: 1.0000 + decayTime: 0.1700 + decayHfRatio: 0.1000 + decayLfRatio: 1.0000 + reflectionsGain: 0.2500 + reflectionsDelay: 0.0010 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2691 + lateReverbDelay: 0.0020 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Room + density: 0.4287 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.5929 + gainLf: 1.0000 + decayTime: 0.4000 + decayHfRatio: 0.8300 + decayLfRatio: 1.0000 + reflectionsGain: 0.1503 + reflectionsDelay: 0.0020 + reflectionsPan: 0,0,0 + lateReverbGain: 1.0629 + lateReverbDelay: 0.0030 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Bathroom + density: 0.1715 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.2512 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.5400 + decayLfRatio: 1.0000 + reflectionsGain: 0.6531 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 3.2734 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: LivingRoom + density: 0.9766 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0010 + gainLf: 1.0000 + decayTime: 0.5000 + decayHfRatio: 0.1000 + decayLfRatio: 1.0000 + reflectionsGain: 0.2051 + reflectionsDelay: 0.0030 + reflectionsPan: 0,0,0 + lateReverbGain: 0.2805 + lateReverbDelay: 0.0040 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: StoneRoom + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 1.0000 + decayTime: 2.3100 + decayHfRatio: 0.6400 + decayLfRatio: 1.0000 + reflectionsGain: 0.4411 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1003 + lateReverbDelay: 0.0170 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Auditorium + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.5781 + gainLf: 1.0000 + decayTime: 4.3200 + decayHfRatio: 0.5900 + decayLfRatio: 1.0000 + reflectionsGain: 0.4032 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7170 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: ConcertHall + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 1.0000 + decayTime: 3.9200 + decayHfRatio: 0.7000 + decayLfRatio: 1.0000 + reflectionsGain: 0.2427 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 0.9977 + lateReverbDelay: 0.0290 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Cave + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 1.0000 + gainLf: 1.0000 + decayTime: 2.9100 + decayHfRatio: 1.3000 + decayLfRatio: 1.0000 + reflectionsGain: 0.5000 + reflectionsDelay: 0.0150 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7063 + lateReverbDelay: 0.0220 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: Arena + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.4477 + gainLf: 1.0000 + decayTime: 7.2400 + decayHfRatio: 0.3300 + decayLfRatio: 1.0000 + reflectionsGain: 0.2612 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 1.0186 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Hangar + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.3162 + gainLf: 1.0000 + decayTime: 10.0500 + decayHfRatio: 0.2300 + decayLfRatio: 1.0000 + reflectionsGain: 0.5000 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2560 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CarpetedHallway + density: 0.4287 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0100 + gainLf: 1.0000 + decayTime: 0.3000 + decayHfRatio: 0.1000 + decayLfRatio: 1.0000 + reflectionsGain: 0.1215 + reflectionsDelay: 0.0020 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1531 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Hallway + density: 0.3645 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.5900 + decayLfRatio: 1.0000 + reflectionsGain: 0.2458 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.6615 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: StoneCorridor + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.7612 + gainLf: 1.0000 + decayTime: 2.7000 + decayHfRatio: 0.7900 + decayLfRatio: 1.0000 + reflectionsGain: 0.2472 + reflectionsDelay: 0.0130 + reflectionsPan: 0,0,0 + lateReverbGain: 1.5758 + lateReverbDelay: 0.0200 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Alley + density: 1.0000 + diffusion: 0.3000 + gain: 0.3162 + gainHf: 0.7328 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.8600 + decayLfRatio: 1.0000 + reflectionsGain: 0.2500 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 0.9954 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.1250 + echoDepth: 0.9500 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Forest + density: 1.0000 + diffusion: 0.3000 + gain: 0.3162 + gainHf: 0.0224 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.5400 + decayLfRatio: 1.0000 + reflectionsGain: 0.0525 + reflectionsDelay: 0.1620 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7682 + lateReverbDelay: 0.0880 + lateReverbPan: 0,0,0 + echoTime: 0.1250 + echoDepth: 1.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: City + density: 1.0000 + diffusion: 0.5000 + gain: 0.3162 + gainHf: 0.3981 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.6700 + decayLfRatio: 1.0000 + reflectionsGain: 0.0730 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1427 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Mountains + density: 1.0000 + diffusion: 0.2700 + gain: 0.3162 + gainHf: 0.0562 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.2100 + decayLfRatio: 1.0000 + reflectionsGain: 0.0407 + reflectionsDelay: 0.3000 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1919 + lateReverbDelay: 0.1000 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 1.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: Quarry + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.3162 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.8300 + decayLfRatio: 1.0000 + reflectionsGain: 0.0000 + reflectionsDelay: 0.0610 + reflectionsPan: 0,0,0 + lateReverbGain: 1.7783 + lateReverbDelay: 0.0250 + lateReverbPan: 0,0,0 + echoTime: 0.1250 + echoDepth: 0.7000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Plain + density: 1.0000 + diffusion: 0.2100 + gain: 0.3162 + gainHf: 0.1000 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.5000 + decayLfRatio: 1.0000 + reflectionsGain: 0.0585 + reflectionsDelay: 0.1790 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1089 + lateReverbDelay: 0.1000 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 1.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: ParkingLot + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 1.0000 + gainLf: 1.0000 + decayTime: 1.6500 + decayHfRatio: 1.5000 + decayLfRatio: 1.0000 + reflectionsGain: 0.2082 + reflectionsDelay: 0.0080 + reflectionsPan: 0,0,0 + lateReverbGain: 0.2652 + lateReverbDelay: 0.0120 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: SewerPipe + density: 0.3071 + diffusion: 0.8000 + gain: 0.3162 + gainHf: 0.3162 + gainLf: 1.0000 + decayTime: 2.8100 + decayHfRatio: 0.1400 + decayLfRatio: 1.0000 + reflectionsGain: 1.6387 + reflectionsDelay: 0.0140 + reflectionsPan: 0,0,0 + lateReverbGain: 3.2471 + lateReverbDelay: 0.0210 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Underwater + density: 0.3645 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0100 + gainLf: 1.0000 + decayTime: 1.4900 + decayHfRatio: 0.1000 + decayLfRatio: 1.0000 + reflectionsGain: 0.5963 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 7.0795 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 1.1800 + modulationDepth: 0.3480 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Drugged + density: 0.4287 + diffusion: 0.5000 + gain: 0.3162 + gainHf: 1.0000 + gainLf: 1.0000 + decayTime: 8.3900 + decayHfRatio: 1.3900 + decayLfRatio: 1.0000 + reflectionsGain: 0.8760 + reflectionsDelay: 0.0020 + reflectionsPan: 0,0,0 + lateReverbGain: 3.1081 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 1.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: Dizzy + density: 0.3645 + diffusion: 0.6000 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 1.0000 + decayTime: 17.2300 + decayHfRatio: 0.5600 + decayLfRatio: 1.0000 + reflectionsGain: 0.1392 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 0.4937 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 1.0000 + modulationTime: 0.8100 + modulationDepth: 0.3100 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: Psychotic + density: 0.0625 + diffusion: 0.5000 + gain: 0.3162 + gainHf: 0.8404 + gainLf: 1.0000 + decayTime: 7.5600 + decayHfRatio: 0.9100 + decayLfRatio: 1.0000 + reflectionsGain: 0.4864 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 2.4378 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 4.0000 + modulationDepth: 1.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +# Castle Presets + +- type: audioPreset + id: CastleSmallRoom + density: 1.0000 + diffusion: 0.8900 + gain: 0.3162 + gainHf: 0.3981 + gainLf: 0.1000 + decayTime: 1.2200 + decayHfRatio: 0.8300 + decayLfRatio: 0.3100 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0220 + reflectionsPan: 0,0,0 + lateReverbGain: 1.9953 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.1380 + echoDepth: 0.0800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleShortPassage + density: 1.0000 + diffusion: 0.8900 + gain: 0.3162 + gainHf: 0.3162 + gainLf: 0.1000 + decayTime: 2.3200 + decayHfRatio: 0.8300 + decayLfRatio: 0.3100 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0230 + lateReverbPan: 0,0,0 + echoTime: 0.1380 + echoDepth: 0.0800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleMediumRoom + density: 1.0000 + diffusion: 0.9300 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.1000 + decayTime: 2.0400 + decayHfRatio: 0.8300 + decayLfRatio: 0.4600 + reflectionsGain: 0.6310 + reflectionsDelay: 0.0220 + reflectionsPan: 0,0,0 + lateReverbGain: 1.5849 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.1550 + echoDepth: 0.0300 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleLargeRoom + density: 1.0000 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.1259 + decayTime: 2.5300 + decayHfRatio: 0.8300 + decayLfRatio: 0.5000 + reflectionsGain: 0.4467 + reflectionsDelay: 0.0340 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0160 + lateReverbPan: 0,0,0 + echoTime: 0.1850 + echoDepth: 0.0700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleLongPassage + density: 1.0000 + diffusion: 0.8900 + gain: 0.3162 + gainHf: 0.3981 + gainLf: 0.1000 + decayTime: 3.4200 + decayHfRatio: 0.8300 + decayLfRatio: 0.3100 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0230 + lateReverbPan: 0,0,0 + echoTime: 0.1380 + echoDepth: 0.0800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleHall + density: 1.0000 + diffusion: 0.8100 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.1778 + decayTime: 3.1400 + decayHfRatio: 0.7900 + decayLfRatio: 0.6200 + reflectionsGain: 0.1778 + reflectionsDelay: 0.0560 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0240 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleCupboard + density: 1.0000 + diffusion: 0.8900 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.1000 + decayTime: 0.6700 + decayHfRatio: 0.8700 + decayLfRatio: 0.3100 + reflectionsGain: 1.4125 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 3.5481 + lateReverbDelay: 0.0070 + lateReverbPan: 0,0,0 + echoTime: 0.1380 + echoDepth: 0.0800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CastleCourtyard + density: 1.0000 + diffusion: 0.4200 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.1995 + decayTime: 2.1300 + decayHfRatio: 0.6100 + decayLfRatio: 0.2300 + reflectionsGain: 0.2239 + reflectionsDelay: 0.1600 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7079 + lateReverbDelay: 0.0360 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.3700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: CastleAlcove + density: 1.0000 + diffusion: 0.8900 + gain: 0.3162 + gainHf: 0.5012 + gainLf: 0.1000 + decayTime: 1.6400 + decayHfRatio: 0.8700 + decayLfRatio: 0.3100 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0340 + lateReverbPan: 0,0,0 + echoTime: 0.1380 + echoDepth: 0.0800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5168.6001 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Factory Presets + +- type: audioPreset + id: FactorySmallRoom + density: 0.3645 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.7943 + gainLf: 0.5012 + decayTime: 1.7200 + decayHfRatio: 0.6500 + decayLfRatio: 1.3100 + reflectionsGain: 0.7079 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.7783 + lateReverbDelay: 0.0240 + lateReverbPan: 0,0,0 + echoTime: 0.1190 + echoDepth: 0.0700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryShortPassage + density: 0.3645 + diffusion: 0.6400 + gain: 0.2512 + gainHf: 0.7943 + gainLf: 0.5012 + decayTime: 2.5300 + decayHfRatio: 0.6500 + decayLfRatio: 1.3100 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0380 + lateReverbPan: 0,0,0 + echoTime: 0.1350 + echoDepth: 0.2300 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryMediumRoom + density: 0.4287 + diffusion: 0.8200 + gain: 0.2512 + gainHf: 0.7943 + gainLf: 0.5012 + decayTime: 2.7600 + decayHfRatio: 0.6500 + decayLfRatio: 1.3100 + reflectionsGain: 0.2818 + reflectionsDelay: 0.0220 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0230 + lateReverbPan: 0,0,0 + echoTime: 0.1740 + echoDepth: 0.0700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryLargeRoom + density: 0.4287 + diffusion: 0.7500 + gain: 0.2512 + gainHf: 0.7079 + gainLf: 0.6310 + decayTime: 4.2400 + decayHfRatio: 0.5100 + decayLfRatio: 1.3100 + reflectionsGain: 0.1778 + reflectionsDelay: 0.0390 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0230 + lateReverbPan: 0,0,0 + echoTime: 0.2310 + echoDepth: 0.0700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryLongPassage + density: 0.3645 + diffusion: 0.6400 + gain: 0.2512 + gainHf: 0.7943 + gainLf: 0.5012 + decayTime: 4.0600 + decayHfRatio: 0.6500 + decayLfRatio: 1.3100 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0370 + lateReverbPan: 0,0,0 + echoTime: 0.1350 + echoDepth: 0.2300 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryHall + density: 0.4287 + diffusion: 0.7500 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.6310 + decayTime: 7.4300 + decayHfRatio: 0.5100 + decayLfRatio: 1.3100 + reflectionsGain: 0.0631 + reflectionsDelay: 0.0730 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0270 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryCupboard + density: 0.3071 + diffusion: 0.6300 + gain: 0.2512 + gainHf: 0.7943 + gainLf: 0.5012 + decayTime: 0.4900 + decayHfRatio: 0.6500 + decayLfRatio: 1.3100 + reflectionsGain: 1.2589 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.9953 + lateReverbDelay: 0.0320 + lateReverbPan: 0,0,0 + echoTime: 0.1070 + echoDepth: 0.0700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryCourtyard + density: 0.3071 + diffusion: 0.5700 + gain: 0.3162 + gainHf: 0.3162 + gainLf: 0.6310 + decayTime: 2.3200 + decayHfRatio: 0.2900 + decayLfRatio: 0.5600 + reflectionsGain: 0.2239 + reflectionsDelay: 0.1400 + reflectionsPan: 0,0,0 + lateReverbGain: 0.3981 + lateReverbDelay: 0.0390 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.2900 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: FactoryAlcove + density: 0.3645 + diffusion: 0.5900 + gain: 0.2512 + gainHf: 0.7943 + gainLf: 0.5012 + decayTime: 3.1400 + decayHfRatio: 0.6500 + decayLfRatio: 1.3100 + reflectionsGain: 1.4125 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.0000 + lateReverbDelay: 0.0380 + lateReverbPan: 0,0,0 + echoTime: 0.1140 + echoDepth: 0.1000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3762.6001 + lfReference: 362.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Ice Palace Presets + +- type: audioPreset + id: IcePalaceSmallRoom + density: 1.0000 + diffusion: 0.8400 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.2818 + decayTime: 1.5100 + decayHfRatio: 1.5300 + decayLfRatio: 0.2700 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.1640 + echoDepth: 0.1400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceShortPassage + density: 1.0000 + diffusion: 0.7500 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.2818 + decayTime: 1.7900 + decayHfRatio: 1.4600 + decayLfRatio: 0.2800 + reflectionsGain: 0.5012 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0190 + lateReverbPan: 0,0,0 + echoTime: 0.1770 + echoDepth: 0.0900 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceMediumRoom + density: 1.0000 + diffusion: 0.8700 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.4467 + decayTime: 2.2200 + decayHfRatio: 1.5300 + decayLfRatio: 0.3200 + reflectionsGain: 0.3981 + reflectionsDelay: 0.0390 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0270 + lateReverbPan: 0,0,0 + echoTime: 0.1860 + echoDepth: 0.1200 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceLargeRoom + density: 1.0000 + diffusion: 0.8100 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.4467 + decayTime: 3.1400 + decayHfRatio: 1.5300 + decayLfRatio: 0.3200 + reflectionsGain: 0.2512 + reflectionsDelay: 0.0390 + reflectionsPan: 0,0,0 + lateReverbGain: 1.0000 + lateReverbDelay: 0.0270 + lateReverbPan: 0,0,0 + echoTime: 0.2140 + echoDepth: 0.1100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceLongPassage + density: 1.0000 + diffusion: 0.7700 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.3981 + decayTime: 3.0100 + decayHfRatio: 1.4600 + decayLfRatio: 0.2800 + reflectionsGain: 0.7943 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0250 + lateReverbPan: 0,0,0 + echoTime: 0.1860 + echoDepth: 0.0400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceHall + density: 1.0000 + diffusion: 0.7600 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.5623 + decayTime: 5.4900 + decayHfRatio: 1.5300 + decayLfRatio: 0.3800 + reflectionsGain: 0.1122 + reflectionsDelay: 0.0540 + reflectionsPan: 0,0,0 + lateReverbGain: 0.6310 + lateReverbDelay: 0.0520 + lateReverbPan: 0,0,0 + echoTime: 0.2260 + echoDepth: 0.1100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceCupboard + density: 1.0000 + diffusion: 0.8300 + gain: 0.3162 + gainHf: 0.5012 + gainLf: 0.2239 + decayTime: 0.7600 + decayHfRatio: 1.5300 + decayLfRatio: 0.2600 + reflectionsGain: 1.1220 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.9953 + lateReverbDelay: 0.0160 + lateReverbPan: 0,0,0 + echoTime: 0.1430 + echoDepth: 0.0800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceCourtyard + density: 1.0000 + diffusion: 0.5900 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.3162 + decayTime: 2.0400 + decayHfRatio: 1.2000 + decayLfRatio: 0.3800 + reflectionsGain: 0.3162 + reflectionsDelay: 0.1730 + reflectionsPan: 0,0,0 + lateReverbGain: 0.3162 + lateReverbDelay: 0.0430 + lateReverbPan: 0,0,0 + echoTime: 0.2350 + echoDepth: 0.4800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: IcePalaceAlcove + density: 1.0000 + diffusion: 0.8400 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.2818 + decayTime: 2.7600 + decayHfRatio: 1.4600 + decayLfRatio: 0.2800 + reflectionsGain: 1.1220 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.1610 + echoDepth: 0.0900 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 12428.5000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Space Station Presets + +- type: audioPreset + id: SpaceStationSmallRoom + density: 0.2109 + diffusion: 0.7000 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.8913 + decayTime: 1.7200 + decayHfRatio: 0.8200 + decayLfRatio: 0.5500 + reflectionsGain: 0.7943 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0130 + lateReverbPan: 0,0,0 + echoTime: 0.1880 + echoDepth: 0.2600 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationShortPassage + density: 0.2109 + diffusion: 0.8700 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.8913 + decayTime: 3.5700 + decayHfRatio: 0.5000 + decayLfRatio: 0.5500 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0160 + lateReverbPan: 0,0,0 + echoTime: 0.1720 + echoDepth: 0.2000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationMediumRoom + density: 0.2109 + diffusion: 0.7500 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.8913 + decayTime: 3.0100 + decayHfRatio: 0.5000 + decayLfRatio: 0.5500 + reflectionsGain: 0.3981 + reflectionsDelay: 0.0340 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0350 + lateReverbPan: 0,0,0 + echoTime: 0.2090 + echoDepth: 0.3100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationLargeRoom + density: 0.3645 + diffusion: 0.8100 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.8913 + decayTime: 3.8900 + decayHfRatio: 0.3800 + decayLfRatio: 0.6100 + reflectionsGain: 0.3162 + reflectionsDelay: 0.0560 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0350 + lateReverbPan: 0,0,0 + echoTime: 0.2330 + echoDepth: 0.2800 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationLongPassage + density: 0.4287 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.8913 + decayTime: 4.6200 + decayHfRatio: 0.6200 + decayLfRatio: 0.5500 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0310 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.2300 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationHall + density: 0.4287 + diffusion: 0.8700 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.8913 + decayTime: 7.1100 + decayHfRatio: 0.3800 + decayLfRatio: 0.6100 + reflectionsGain: 0.1778 + reflectionsDelay: 0.1000 + reflectionsPan: 0,0,0 + lateReverbGain: 0.6310 + lateReverbDelay: 0.0470 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.2500 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationCupboard + density: 0.1715 + diffusion: 0.5600 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.8913 + decayTime: 0.7900 + decayHfRatio: 0.8100 + decayLfRatio: 0.5500 + reflectionsGain: 1.4125 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.7783 + lateReverbDelay: 0.0180 + lateReverbPan: 0,0,0 + echoTime: 0.1810 + echoDepth: 0.3100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SpaceStationAlcove + density: 0.2109 + diffusion: 0.7800 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.8913 + decayTime: 1.1600 + decayHfRatio: 0.8100 + decayLfRatio: 0.5500 + reflectionsGain: 1.4125 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 1.0000 + lateReverbDelay: 0.0180 + lateReverbPan: 0,0,0 + echoTime: 0.1920 + echoDepth: 0.2100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 3316.1001 + lfReference: 458.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Wooden Galleon Presets + +- type: audioPreset + id: WoodenSmallRoom + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1122 + gainLf: 0.3162 + decayTime: 0.7900 + decayHfRatio: 0.3200 + decayLfRatio: 0.8700 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0320 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0290 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenShortPassage + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1259 + gainLf: 0.3162 + decayTime: 1.7500 + decayHfRatio: 0.5000 + decayLfRatio: 0.8700 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 0.6310 + lateReverbDelay: 0.0240 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenMediumRoom + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1000 + gainLf: 0.2818 + decayTime: 1.4700 + decayHfRatio: 0.4200 + decayLfRatio: 0.8200 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0490 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0290 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenLargeRoom + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0891 + gainLf: 0.2818 + decayTime: 2.6500 + decayHfRatio: 0.3300 + decayLfRatio: 0.8200 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0660 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7943 + lateReverbDelay: 0.0490 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenLongPassage + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1000 + gainLf: 0.3162 + decayTime: 1.9900 + decayHfRatio: 0.4000 + decayLfRatio: 0.7900 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 0.4467 + lateReverbDelay: 0.0360 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenHall + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0794 + gainLf: 0.2818 + decayTime: 3.4500 + decayHfRatio: 0.3000 + decayLfRatio: 0.8200 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0880 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7943 + lateReverbDelay: 0.0630 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenCupboard + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1413 + gainLf: 0.3162 + decayTime: 0.5600 + decayHfRatio: 0.4600 + decayLfRatio: 0.9100 + reflectionsGain: 1.1220 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0280 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenCourtyard + density: 1.0000 + diffusion: 0.6500 + gain: 0.3162 + gainHf: 0.0794 + gainLf: 0.3162 + decayTime: 1.7900 + decayHfRatio: 0.3500 + decayLfRatio: 0.7900 + reflectionsGain: 0.5623 + reflectionsDelay: 0.1230 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1000 + lateReverbDelay: 0.0320 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: WoodenAlcove + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1259 + gainLf: 0.3162 + decayTime: 1.2200 + decayHfRatio: 0.6200 + decayLfRatio: 0.9100 + reflectionsGain: 1.1220 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7079 + lateReverbDelay: 0.0240 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4705.0000 + lfReference: 99.6000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Sports Presets + +- type: audioPreset + id: SportEmptyStadium + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.7943 + decayTime: 6.2600 + decayHfRatio: 0.5100 + decayLfRatio: 1.1000 + reflectionsGain: 0.0631 + reflectionsDelay: 0.1830 + reflectionsPan: 0,0,0 + lateReverbGain: 0.3981 + lateReverbDelay: 0.0380 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SportSquashCourt + density: 1.0000 + diffusion: 0.7500 + gain: 0.3162 + gainHf: 0.3162 + gainLf: 0.7943 + decayTime: 2.2200 + decayHfRatio: 0.9100 + decayLfRatio: 1.1600 + reflectionsGain: 0.4467 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7943 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.1260 + echoDepth: 0.1900 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 7176.8999 + lfReference: 211.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SportSmallSwimmingPool + density: 1.0000 + diffusion: 0.7000 + gain: 0.3162 + gainHf: 0.7943 + gainLf: 0.8913 + decayTime: 2.7600 + decayHfRatio: 1.2500 + decayLfRatio: 1.1400 + reflectionsGain: 0.6310 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7943 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.1790 + echoDepth: 0.1500 + modulationTime: 0.8950 + modulationDepth: 0.1900 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: SportLargeSwimmingPool + density: 1.0000 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.7943 + gainLf: 1.0000 + decayTime: 5.4900 + decayHfRatio: 1.3100 + decayLfRatio: 1.1400 + reflectionsGain: 0.4467 + reflectionsDelay: 0.0390 + reflectionsPan: 0,0,0 + lateReverbGain: 0.5012 + lateReverbDelay: 0.0490 + lateReverbPan: 0,0,0 + echoTime: 0.2220 + echoDepth: 0.5500 + modulationTime: 1.1590 + modulationDepth: 0.2100 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: SportGymnasium + density: 1.0000 + diffusion: 0.8100 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.8913 + decayTime: 3.1400 + decayHfRatio: 1.0600 + decayLfRatio: 1.3500 + reflectionsGain: 0.3981 + reflectionsDelay: 0.0290 + reflectionsPan: 0,0,0 + lateReverbGain: 0.5623 + lateReverbDelay: 0.0450 + lateReverbPan: 0,0,0 + echoTime: 0.1460 + echoDepth: 0.1400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 7176.8999 + lfReference: 211.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SportFullStadium + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0708 + gainLf: 0.7943 + decayTime: 5.2500 + decayHfRatio: 0.1700 + decayLfRatio: 0.8000 + reflectionsGain: 0.1000 + reflectionsDelay: 0.1880 + reflectionsPan: 0,0,0 + lateReverbGain: 0.2818 + lateReverbDelay: 0.0380 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SportStadiumTannoy + density: 1.0000 + diffusion: 0.7800 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.5012 + decayTime: 2.5300 + decayHfRatio: 0.8800 + decayLfRatio: 0.6800 + reflectionsGain: 0.2818 + reflectionsDelay: 0.2300 + reflectionsPan: 0,0,0 + lateReverbGain: 0.5012 + lateReverbDelay: 0.0630 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.2000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Prefab Presets + +- type: audioPreset + id: PrefabWorkshop + density: 0.4287 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1413 + gainLf: 0.3981 + decayTime: 0.7600 + decayHfRatio: 1.0000 + decayLfRatio: 1.0000 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0120 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: PrefabSchoolroom + density: 0.4022 + diffusion: 0.6900 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.5012 + decayTime: 0.9800 + decayHfRatio: 0.4500 + decayLfRatio: 0.1800 + reflectionsGain: 1.4125 + reflectionsDelay: 0.0170 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0150 + lateReverbPan: 0,0,0 + echoTime: 0.0950 + echoDepth: 0.1400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 7176.8999 + lfReference: 211.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: PrefabPractiseRoom + density: 0.4022 + diffusion: 0.8700 + gain: 0.3162 + gainHf: 0.3981 + gainLf: 0.5012 + decayTime: 1.1200 + decayHfRatio: 0.5600 + decayLfRatio: 0.1800 + reflectionsGain: 1.2589 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0110 + lateReverbPan: 0,0,0 + echoTime: 0.0950 + echoDepth: 0.1400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 7176.8999 + lfReference: 211.2000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: PrefabOuthouse + density: 1.0000 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.1122 + gainLf: 0.1585 + decayTime: 1.3800 + decayHfRatio: 0.3800 + decayLfRatio: 0.3500 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0240 + reflectionsPan: 0, 0, 0 + lateReverbGain: 0.6310 + lateReverbDelay: 0.0440 + lateReverbPan: 0,0,0 + echoTime: 0.1210 + echoDepth: 0.1700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 107.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: PrefabCaravan + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.0891 + gainLf: 0.1259 + decayTime: 0.4300 + decayHfRatio: 1.5000 + decayLfRatio: 1.0000 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0120 + reflectionsPan: 0,0,0 + lateReverbGain: 1.9953 + lateReverbDelay: 0.0120 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +# Dome and Pipe Presets + +- type: audioPreset + id: DomeTomb + density: 1.0000 + diffusion: 0.7900 + gain: 0.3162 + gainHf: 0.3548 + gainLf: 0.2239 + decayTime: 4.1800 + decayHfRatio: 0.2100 + decayLfRatio: 0.1000 + reflectionsGain: 0.3868 + reflectionsDelay: 0.0300 + reflectionsPan: 0,0,0 + lateReverbGain: 1.6788 + lateReverbDelay: 0.0220 + lateReverbPan: 0,0,0 + echoTime: 0.1770 + echoDepth: 0.1900 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 20.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: PipeSmall + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.3548 + gainLf: 0.2239 + decayTime: 5.0400 + decayHfRatio: 0.1000 + decayLfRatio: 0.1000 + reflectionsGain: 0.5012 + reflectionsDelay: 0.0320 + reflectionsPan: 0,0,0 + lateReverbGain: 2.5119 + lateReverbDelay: 0.0150 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 20.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: DomeSaintPauls + density: 1.0000 + diffusion: 0.8700 + gain: 0.3162 + gainHf: 0.3548 + gainLf: 0.2239 + decayTime: 10.4800 + decayHfRatio: 0.1900 + decayLfRatio: 0.1000 + reflectionsGain: 0.1778 + reflectionsDelay: 0.0900 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0420 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.1200 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 20.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: PipeLongThin + density: 0.2560 + diffusion: 0.9100 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.2818 + decayTime: 9.2100 + decayHfRatio: 0.1800 + decayLfRatio: 0.1000 + reflectionsGain: 0.7079 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7079 + lateReverbDelay: 0.0220 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 20.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: PipeLarge + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.3548 + gainLf: 0.2239 + decayTime: 8.4500 + decayHfRatio: 0.1000 + decayLfRatio: 0.1000 + reflectionsGain: 0.3981 + reflectionsDelay: 0.0460 + reflectionsPan: 0,0,0 + lateReverbGain: 1.5849 + lateReverbDelay: 0.0320 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 20.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: PipeResonant + density: 0.1373 + diffusion: 0.9100 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.2818 + decayTime: 6.8100 + decayHfRatio: 0.1800 + decayLfRatio: 0.1000 + reflectionsGain: 0.7079 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.0000 + lateReverbDelay: 0.0220 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 20.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +# Outdoors Presets + +- type: audioPreset + id: OutdoorsBackyard + density: 1.0000 + diffusion: 0.4500 + gain: 0.3162 + gainHf: 0.2512 + gainLf: 0.5012 + decayTime: 1.1200 + decayHfRatio: 0.3400 + decayLfRatio: 0.4600 + reflectionsGain: 0.4467 + reflectionsDelay: 0.0690 + reflectionsPan: 0, 0, 0 + lateReverbGain: 0.7079 + lateReverbDelay: 0.0230 + lateReverbPan: 0,0,0 + echoTime: 0.2180 + echoDepth: 0.3400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4399.1001 + lfReference: 242.9000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: OutdoorsRollingPlains + density: 1.0000 + diffusion: 0.0000 + gain: 0.3162 + gainHf: 0.0112 + gainLf: 0.6310 + decayTime: 2.1300 + decayHfRatio: 0.2100 + decayLfRatio: 0.4600 + reflectionsGain: 0.1778 + reflectionsDelay: 0.3000 + reflectionsPan: 0,0,0 + lateReverbGain: 0.4467 + lateReverbDelay: 0.0190 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 1.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4399.1001 + lfReference: 242.9000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: OutdoorsDeepCanyon + density: 1.0000 + diffusion: 0.7400 + gain: 0.3162 + gainHf: 0.1778 + gainLf: 0.6310 + decayTime: 3.8900 + decayHfRatio: 0.2100 + decayLfRatio: 0.4600 + reflectionsGain: 0.3162 + reflectionsDelay: 0.2230 + reflectionsPan: 0,0,0 + lateReverbGain: 0.3548 + lateReverbDelay: 0.0190 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 1.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4399.1001 + lfReference: 242.9000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: OutdoorsCreek + density: 1.0000 + diffusion: 0.3500 + gain: 0.3162 + gainHf: 0.1778 + gainLf: 0.5012 + decayTime: 2.1300 + decayHfRatio: 0.2100 + decayLfRatio: 0.4600 + reflectionsGain: 0.3981 + reflectionsDelay: 0.1150 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1995 + lateReverbDelay: 0.0310 + lateReverbPan: 0,0,0 + echoTime: 0.2180 + echoDepth: 0.3400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 4399.1001 + lfReference: 242.9000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: OutdoorsValley + density: 1.0000 + diffusion: 0.2800 + gain: 0.3162 + gainHf: 0.0282 + gainLf: 0.1585 + decayTime: 2.8800 + decayHfRatio: 0.2600 + decayLfRatio: 0.3500 + reflectionsGain: 0.1413 + reflectionsDelay: 0.2630 + reflectionsPan: 0,0,0 + lateReverbGain: 0.3981 + lateReverbDelay: 0.1000 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.3400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 107.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +# Mood Presets + +- type: audioPreset + id: MoodHeaven + density: 1.0000 + diffusion: 0.9400 + gain: 0.3162 + gainHf: 0.7943 + gainLf: 0.4467 + decayTime: 5.0400 + decayHfRatio: 1.1200 + decayLfRatio: 0.5600 + reflectionsGain: 0.2427 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0290 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0800 + modulationTime: 2.7420 + modulationDepth: 0.0500 + airAbsorptionGainHf: 0.9977 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: MoodHell + density: 1.0000 + diffusion: 0.5700 + gain: 0.3162 + gainHf: 0.3548 + gainLf: 0.4467 + decayTime: 3.5700 + decayHfRatio: 0.4900 + decayLfRatio: 2.0000 + reflectionsGain: 0.0000 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.1100 + echoDepth: 0.0400 + modulationTime: 2.1090 + modulationDepth: 0.5200 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 139.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: MoodMemory + density: 1.0000 + diffusion: 0.8500 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 0.3548 + decayTime: 4.0600 + decayHfRatio: 0.8200 + decayLfRatio: 0.5600 + reflectionsGain: 0.0398 + reflectionsDelay: 0.0000 + reflectionsPan: 0,0,0 + lateReverbGain: 1.1220 + lateReverbDelay: 0.0000 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.4740 + modulationDepth: 0.4500 + airAbsorptionGainHf: 0.9886 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +# Driving Presets + +- type: audioPreset + id: DrivingCommentator + density: 1.0000 + diffusion: 0.0000 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 0.5012 + decayTime: 2.4200 + decayHfRatio: 0.8800 + decayLfRatio: 0.6800 + reflectionsGain: 0.1995 + reflectionsDelay: 0.0930 + reflectionsPan: 0,0,0 + lateReverbGain: 0.2512 + lateReverbDelay: 0.0170 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 1.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9886 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: DrivingPitGarage + density: 0.4287 + diffusion: 0.5900 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.5623 + decayTime: 1.7200 + decayHfRatio: 0.9300 + decayLfRatio: 0.8700 + reflectionsGain: 0.5623 + reflectionsDelay: 0.0000 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0160 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.1100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: DrivingInCarRacer + density: 0.0832 + diffusion: 0.8000 + gain: 0.3162 + gainHf: 1.0000 + gainLf: 0.7943 + decayTime: 0.1700 + decayHfRatio: 2.0000 + decayLfRatio: 0.4100 + reflectionsGain: 1.7783 + reflectionsDelay: 0.0070 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7079 + lateReverbDelay: 0.0150 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 10268.2002 + lfReference: 251.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: DrivingInCarSports + density: 0.0832 + diffusion: 0.8000 + gain: 0.3162 + gainHf: 0.6310 + gainLf: 1.0000 + decayTime: 0.1700 + decayHfRatio: 0.7500 + decayLfRatio: 0.4100 + reflectionsGain: 1.0000 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 0.5623 + lateReverbDelay: 0.0000 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 10268.2002 + lfReference: 251.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: DrivingInCarLuxury + density: 0.2560 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.1000 + gainLf: 0.5012 + decayTime: 0.1300 + decayHfRatio: 0.4100 + decayLfRatio: 0.4600 + reflectionsGain: 0.7943 + reflectionsDelay: 0.0100 + reflectionsPan: 0,0,0 + lateReverbGain: 1.5849 + lateReverbDelay: 0.0100 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 10268.2002 + lfReference: 251.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: DrivingFullGrandstand + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.6310 + decayTime: 3.0100 + decayHfRatio: 1.3700 + decayLfRatio: 1.2800 + reflectionsGain: 0.3548 + reflectionsDelay: 0.0900 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1778 + lateReverbDelay: 0.0490 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 10420.2002 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: DrivingEmptyGrandstand + density: 1.0000 + diffusion: 1.0000 + gain: 0.3162 + gainHf: 1.0000 + gainLf: 0.7943 + decayTime: 4.6200 + decayHfRatio: 1.7500 + decayLfRatio: 1.4000 + reflectionsGain: 0.2082 + reflectionsDelay: 0.0900 + reflectionsPan: 0,0,0 + lateReverbGain: 0.2512 + lateReverbDelay: 0.0490 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 10420.2002 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: DrivingTunnel + density: 1.0000 + diffusion: 0.8100 + gain: 0.3162 + gainHf: 0.3981 + gainLf: 0.8913 + decayTime: 3.4200 + decayHfRatio: 0.9400 + decayLfRatio: 1.3100 + reflectionsGain: 0.7079 + reflectionsDelay: 0.0510 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7079 + lateReverbDelay: 0.0470 + lateReverbPan: 0,0,0 + echoTime: 0.2140 + echoDepth: 0.0500 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 155.3000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# City Presets + +- type: audioPreset + id: CityStreets + density: 1.0000 + diffusion: 0.7800 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.8913 + decayTime: 1.7900 + decayHfRatio: 1.1200 + decayLfRatio: 0.9100 + reflectionsGain: 0.2818 + reflectionsDelay: 0.0460 + reflectionsPan: 0,0,0 + lateReverbGain: 0.1995 + lateReverbDelay: 0.0280 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.2000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CitySubway + density: 1.0000 + diffusion: 0.7400 + gain: 0.3162 + gainHf: 0.7079 + gainLf: 0.8913 + decayTime: 3.0100 + decayHfRatio: 1.2300 + decayLfRatio: 0.9100 + reflectionsGain: 0.7079 + reflectionsDelay: 0.0460 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0280 + lateReverbPan: 0,0,0 + echoTime: 0.1250 + echoDepth: 0.2100 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CityMuseum + density: 1.0000 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.1778 + gainLf: 0.1778 + decayTime: 3.2800 + decayHfRatio: 1.4000 + decayLfRatio: 0.5700 + reflectionsGain: 0.2512 + reflectionsDelay: 0.0390 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0340 + lateReverbPan: 0,0,0 + echoTime: 0.1300 + echoDepth: 0.1700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 107.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: CityLibrary + density: 1.0000 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.2818 + gainLf: 0.0891 + decayTime: 2.7600 + decayHfRatio: 0.8900 + decayLfRatio: 0.4100 + reflectionsGain: 0.3548 + reflectionsDelay: 0.0290 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0200 + lateReverbPan: 0,0,0 + echoTime: 0.1300 + echoDepth: 0.1700 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9943 + hfReference: 2854.3999 + lfReference: 107.5000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + + +- type: audioPreset + id: CityUnderpass + density: 1.0000 + diffusion: 0.8200 + gain: 0.3162 + gainHf: 0.4467 + gainLf: 0.8913 + decayTime: 3.5700 + decayHfRatio: 1.1200 + decayLfRatio: 0.9100 + reflectionsGain: 0.3981 + reflectionsDelay: 0.0590 + reflectionsPan: 0,0,0 + lateReverbGain: 0.8913 + lateReverbDelay: 0.0370 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.1400 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9920 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: CityAbandoned + density: 1.0000 + diffusion: 0.6900 + gain: 0.3162 + gainHf: 0.7943 + gainLf: 0.8913 + decayTime: 3.2800 + decayHfRatio: 1.1700 + decayLfRatio: 0.9100 + reflectionsGain: 0.4467 + reflectionsDelay: 0.0440 + reflectionsPan: 0,0,0 + lateReverbGain: 0.2818 + lateReverbDelay: 0.0240 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.2000 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9966 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +# Misc. Presets + +- type: audioPreset + id: DustyRoom + density: 0.3645 + diffusion: 0.5600 + gain: 0.3162 + gainHf: 0.7943 + gainLf: 0.7079 + decayTime: 1.7900 + decayHfRatio: 0.3800 + decayLfRatio: 0.2100 + reflectionsGain: 0.5012 + reflectionsDelay: 0.0020 + reflectionsPan: 0,0,0 + lateReverbGain: 1.2589 + lateReverbDelay: 0.0060 + lateReverbPan: 0,0,0 + echoTime: 0.2020 + echoDepth: 0.0500 + modulationTime: 0.2500 + modulationDepth: 0.0000 + airAbsorptionGainHf: 0.9886 + hfReference: 13046.0000 + lfReference: 163.3000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: Chapel + density: 1.0000 + diffusion: 0.8400 + gain: 0.3162 + gainHf: 0.5623 + gainLf: 1.0000 + decayTime: 4.6200 + decayHfRatio: 0.6400 + decayLfRatio: 1.2300 + reflectionsGain: 0.4467 + reflectionsDelay: 0.0320 + reflectionsPan: 0,0,0 + lateReverbGain: 0.7943 + lateReverbDelay: 0.0490 + lateReverbPan: 0,0,0 + echoTime: 0.2500 + echoDepth: 0.0000 + modulationTime: 0.2500 + modulationDepth: 0.1100 + airAbsorptionGainHf: 0.9943 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 1 + + +- type: audioPreset + id: SmallWaterRoom + density: 1.0000 + diffusion: 0.7000 + gain: 0.3162 + gainHf: 0.4477 + gainLf: 1.0000 + decayTime: 1.5100 + decayHfRatio: 1.2500 + decayLfRatio: 1.1400 + reflectionsGain: 0.8913 + reflectionsDelay: 0.0200 + reflectionsPan: 0,0,0 + lateReverbGain: 1.4125 + lateReverbDelay: 0.0300 + lateReverbPan: 0,0,0 + echoTime: 0.1790 + echoDepth: 0.1500 + modulationTime: 0.8950 + modulationDepth: 0.1900 + airAbsorptionGainHf: 0.9920 + hfReference: 5000.0000 + lfReference: 250.0000 + roomRolloffFactor: 0.0000 + decayHfLimit: 0 + diff --git a/Resources/Locale/en-US/commands.ftl b/Resources/Locale/en-US/commands.ftl index 71de45e6c..896973262 100644 --- a/Resources/Locale/en-US/commands.ftl +++ b/Resources/Locale/en-US/commands.ftl @@ -561,3 +561,7 @@ cmd-vfs_ls-hint-path = cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites cmd-reloadtiletextures-help = Usage: reloadtiletextures + +cmd-audio_length-desc = Shows the length of an audio file +cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name } +cmd-audio_length-arg-file-name = diff --git a/Robust.Client/Animations/AnimationTrackPlaySound.cs b/Robust.Client/Animations/AnimationTrackPlaySound.cs index 1ffd5f207..be5051a4d 100644 --- a/Robust.Client/Animations/AnimationTrackPlaySound.cs +++ b/Robust.Client/Animations/AnimationTrackPlaySound.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using Robust.Client.Audio; using Robust.Client.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Player; namespace Robust.Client.Animations @@ -37,7 +39,12 @@ namespace Robust.Client.Animations var keyFrame = KeyFrames[keyFrameIndex]; - SoundSystem.Play(keyFrame.Resource, Filter.Local(), entity, keyFrame.AudioParamsFunc.Invoke()); + var audioParams = keyFrame.AudioParamsFunc.Invoke(); + var audio = new SoundPathSpecifier(keyFrame.Resource) + { + Params = audioParams + }; + IoCManager.Resolve().GetEntitySystem().PlayEntity(audio, Filter.Local(), entity, true); } return (keyFrameIndex, playingTime); diff --git a/Robust.Client/Audio/AudioManager.ALDisposeQueues.cs b/Robust.Client/Audio/AudioManager.ALDisposeQueues.cs new file mode 100644 index 000000000..c60448f07 --- /dev/null +++ b/Robust.Client/Audio/AudioManager.ALDisposeQueues.cs @@ -0,0 +1,58 @@ +using System.Collections.Concurrent; +using OpenTK.Audio.OpenAL; + +namespace Robust.Client.Audio; + +internal partial class AudioManager +{ + // Used to track audio sources that were disposed in the finalizer thread, + // so we need to properly send them off in the main thread. + private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _sourceDisposeQueue = new(); + private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new(); + private readonly ConcurrentQueue _bufferDisposeQueue = new(); + + public void FlushALDisposeQueues() + { + // Clear out finalized audio sources. + while (_sourceDisposeQueue.TryDequeue(out var handles)) + { + OpenALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle); + if (IsEfxSupported) RemoveEfx(handles); + AL.DeleteSource(handles.sourceHandle); + _checkAlError(); + _audioSources.Remove(handles.sourceHandle); + } + + // Clear out finalized buffered audio sources. + while (_bufferedSourceDisposeQueue.TryDequeue(out var handles)) + { + OpenALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle); + if (IsEfxSupported) RemoveEfx(handles); + AL.DeleteSource(handles.sourceHandle); + _checkAlError(); + _bufferedAudioSources.Remove(handles.sourceHandle); + } + + // Clear out finalized audio buffers. + while (_bufferDisposeQueue.TryDequeue(out var handle)) + { + AL.DeleteBuffer(handle); + _checkAlError(); + } + } + + internal void DeleteSourceOnMainThread(int sourceHandle, int filterHandle) + { + _sourceDisposeQueue.Enqueue((sourceHandle, filterHandle)); + } + + internal void DeleteBufferedSourceOnMainThread(int bufferedSourceHandle, int filterHandle) + { + _bufferedSourceDisposeQueue.Enqueue((bufferedSourceHandle, filterHandle)); + } + + internal void DeleteAudioBufferOnMainThread(int bufferHandle) + { + _bufferDisposeQueue.Enqueue(bufferHandle); + } +} diff --git a/Robust.Client/Audio/AudioManager.Public.cs b/Robust.Client/Audio/AudioManager.Public.cs new file mode 100644 index 000000000..4beed00f2 --- /dev/null +++ b/Robust.Client/Audio/AudioManager.Public.cs @@ -0,0 +1,339 @@ +using System; +using System.IO; +using System.Numerics; +using System.Threading; +using OpenTK.Audio.OpenAL; +using Robust.Client.Audio.Sources; +using Robust.Client.Graphics; +using Robust.Shared.Audio; +using Robust.Shared.Audio.AudioLoading; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Maths; + +namespace Robust.Client.Audio; + +internal partial class AudioManager +{ + private float _zOffset; + + public void SetZOffset(float offset) + { + _zOffset = offset; + } + + /// + public float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance) + { + switch (_attenuation) + { + case Attenuation.LinearDistance: + return 1 - rolloffFactor * (distance - referenceDistance) / (maxDistance - referenceDistance); + case Attenuation.LinearDistanceClamped: + distance = MathF.Max(referenceDistance, MathF.Min(distance, maxDistance)); + return 1 - rolloffFactor * (distance - referenceDistance) / (maxDistance - referenceDistance); + default: + // TODO: If you see this you can implement + throw new NotImplementedException(); + } + } + + public void InitializePostWindowing() + { + _gameThread = Thread.CurrentThread; + InitializeAudio(); + } + + public void Shutdown() + { + DisposeAllAudio(); + + if (_openALContext != ALContext.Null) + { + ALC.MakeContextCurrent(ALContext.Null); + + ALC.DestroyContext(_openALContext); + } + + if (_openALDevice != IntPtr.Zero) + { + ALC.CloseDevice(_openALDevice); + } + } + + /// + public void SetPosition(Vector2 position) + { + AL.Listener(ALListener3f.Position, position.X, position.Y, _zOffset); + } + + /// + public void SetRotation(Angle angle) + { + var vec = angle.ToVec(); + + // Default orientation: at: (0, 0, -1) up: (0, 1, 0) + var at = new OpenTK.Mathematics.Vector3(0f, 0f, -1f); + var up = new OpenTK.Mathematics.Vector3(vec.Y, vec.X, 0f); + AL.Listener(ALListenerfv.Orientation, new []{0, 0, -1, vec.X, vec.Y, 0}); + AL.Listener(ALListenerfv.Orientation, ref at, ref up); + } + + /// + public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null) + { + var vorbis = AudioLoaderOgg.LoadAudioData(stream); + + var buffer = AL.GenBuffer(); + + ALFormat format; + // NVorbis only supports loading into floats. + // If this becomes a problem due to missing extension support (doubt it but ok), + // check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM. + if (vorbis.Channels == 1) + { + format = ALFormat.MonoFloat32Ext; + } + else if (vorbis.Channels == 2) + { + format = ALFormat.StereoFloat32Ext; + } + else + { + throw new InvalidOperationException("Unable to load audio with more than 2 channels."); + } + + unsafe + { + fixed (float* ptr = vorbis.Data.Span) + { + AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float), + (int) vorbis.SampleRate); + } + } + + _checkAlError(); + + var handle = new ClydeHandle(_audioSampleBuffers.Count); + _audioSampleBuffers.Add(new LoadedAudioSample(buffer)); + var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate); + return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist); + } + + /// + public AudioStream LoadAudioWav(Stream stream, string? name = null) + { + var wav = AudioLoaderWav.LoadAudioData(stream); + + var buffer = AL.GenBuffer(); + + ALFormat format; + if (wav.BitsPerSample == 16) + { + if (wav.NumChannels == 1) + { + format = ALFormat.Mono16; + } + else if (wav.NumChannels == 2) + { + format = ALFormat.Stereo16; + } + else + { + throw new InvalidOperationException("Unable to load audio with more than 2 channels."); + } + } + else if (wav.BitsPerSample == 8) + { + if (wav.NumChannels == 1) + { + format = ALFormat.Mono8; + } + else if (wav.NumChannels == 2) + { + format = ALFormat.Stereo8; + } + else + { + throw new InvalidOperationException("Unable to load audio with more than 2 channels."); + } + } + else + { + throw new InvalidOperationException("Unable to load wav with bits per sample different from 8 or 16"); + } + + unsafe + { + fixed (byte* ptr = wav.Data.Span) + { + AL.BufferData(buffer, format, (IntPtr) ptr, wav.Data.Length, wav.SampleRate); + } + } + + _checkAlError(); + + var handle = new ClydeHandle(_audioSampleBuffers.Count); + _audioSampleBuffers.Add(new LoadedAudioSample(buffer)); + var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate); + return new AudioStream(handle, length, wav.NumChannels, name); + } + + /// + public AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null) + { + var fmt = channels switch + { + 1 => ALFormat.Mono16, + 2 => ALFormat.Stereo16, + _ => throw new ArgumentOutOfRangeException( + nameof(channels), "Only stereo and mono is currently supported") + }; + + var buffer = AL.GenBuffer(); + _checkAlError(); + + unsafe + { + fixed (short* ptr = samples) + { + AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate); + } + } + + _checkAlError(); + + var handle = new ClydeHandle(_audioSampleBuffers.Count); + var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate); + _audioSampleBuffers.Add(new LoadedAudioSample(buffer)); + return new AudioStream(handle, length, channels, name); + } + + public void SetMasterVolume(float newVolume) + { + AL.Listener(ALListenerf.Gain, newVolume); + } + + public void SetAttenuation(Attenuation attenuation) + { + switch (attenuation) + { + case Attenuation.NoAttenuation: + AL.DistanceModel(ALDistanceModel.None); + break; + case Attenuation.InverseDistance: + AL.DistanceModel(ALDistanceModel.InverseDistance); + break; + case Attenuation.InverseDistanceClamped: + AL.DistanceModel(ALDistanceModel.InverseDistanceClamped); + break; + case Attenuation.LinearDistance: + AL.DistanceModel(ALDistanceModel.LinearDistance); + break; + case Attenuation.LinearDistanceClamped: + AL.DistanceModel(ALDistanceModel.LinearDistanceClamped); + break; + case Attenuation.ExponentDistance: + AL.DistanceModel(ALDistanceModel.ExponentDistance); + break; + case Attenuation.ExponentDistanceClamped: + AL.DistanceModel(ALDistanceModel.ExponentDistanceClamped); + break; + default: + throw new ArgumentOutOfRangeException($"No implementation to set {attenuation.ToString()} for DistanceModel!"); + } + + _attenuation = attenuation; + OpenALSawmill.Info($"Set audio attenuation to {attenuation.ToString()}"); + } + + internal void RemoveAudioSource(int handle) + { + _audioSources.Remove(handle); + } + + internal void RemoveBufferedAudioSource(int handle) + { + _bufferedAudioSources.Remove(handle); + } + + public IAudioSource? CreateAudioSource(AudioStream stream) + { + var source = AL.GenSource(); + + if (!AL.IsSource(source)) + { + OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace); + return null; + } + + // ReSharper disable once PossibleInvalidOperationException + // TODO: This really shouldn't be indexing based on the ClydeHandle... + AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value].BufferHandle); + + var audioSource = new AudioSource(this, source, stream); + _audioSources.Add(source, new WeakReference(audioSource)); + return audioSource; + } + + public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false) + { + var source = AL.GenSource(); + + if (!AL.IsSource(source)) + { + OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace); + } + + // ReSharper disable once PossibleInvalidOperationException + + var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio); + _bufferedAudioSources.Add(source, new WeakReference(audioSource)); + return audioSource; + } + + /// + public void StopAllAudio() + { + foreach (var source in _audioSources.Values) + { + if (source.TryGetTarget(out var target)) + { + target.Playing = false; + } + } + + foreach (var source in _bufferedAudioSources.Values) + { + if (source.TryGetTarget(out var target)) + { + target.Playing = false; + } + } + } + + public void DisposeAllAudio() + { + // TODO: Do we even need to stop? + foreach (var source in _audioSources.Values) + { + if (source.TryGetTarget(out var target)) + { + target.Playing = false; + target.Dispose(); + } + } + + _audioSources.Clear(); + + foreach (var source in _bufferedAudioSources.Values) + { + if (source.TryGetTarget(out var target)) + { + target.Playing = false; + target.Dispose(); + } + } + + _bufferedAudioSources.Clear(); + } +} diff --git a/Robust.Client/Audio/AudioManager.cs b/Robust.Client/Audio/AudioManager.cs new file mode 100644 index 000000000..3d9191617 --- /dev/null +++ b/Robust.Client/Audio/AudioManager.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using OpenTK.Audio.OpenAL; +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Client.Audio.Sources; +using Robust.Shared; +using Robust.Shared.Audio; +using Robust.Shared.Configuration; +using Robust.Shared.Log; +using Robust.Shared.Utility; + +namespace Robust.Client.Audio; + +internal sealed partial class AudioManager : IAudioInternal +{ + [Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!; + [Shared.IoC.Dependency] private readonly ILogManager _logMan = default!; + + private Thread? _gameThread; + + private ALDevice _openALDevice; + private ALContext _openALContext; + + private readonly List _audioSampleBuffers = new(); + + private readonly Dictionary> _audioSources = + new(); + + private readonly Dictionary> _bufferedAudioSources = + new(); + + private readonly HashSet _alcDeviceExtensions = new(); + private readonly HashSet _alContextExtensions = new(); + private Attenuation _attenuation; + + public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension); + public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension); + + internal bool IsEfxSupported; + + internal ISawmill OpenALSawmill = default!; + + private void _audioCreateContext() + { + unsafe + { + _openALContext = ALC.CreateContext(_openALDevice, (int*) 0); + } + + ALC.MakeContextCurrent(_openALContext); + _checkAlcError(_openALDevice); + _checkAlError(); + + // Load up AL context extensions. + var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? ""; + foreach (var extension in s.Split(' ')) + { + _alContextExtensions.Add(extension); + } + + OpenALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor)); + OpenALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer)); + OpenALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version)); + } + + private bool _audioOpenDevice() + { + var preferredDevice = _cfg.GetCVar(CVars.AudioDevice); + + // Open device. + if (!string.IsNullOrEmpty(preferredDevice)) + { + _openALDevice = ALC.OpenDevice(preferredDevice); + if (_openALDevice == IntPtr.Zero) + { + OpenALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.", + preferredDevice, ALC.GetError(ALDevice.Null)); + + _openALDevice = ALC.OpenDevice(null); + } + } + else + { + _openALDevice = ALC.OpenDevice(null); + } + + _checkAlcError(_openALDevice); + + if (_openALDevice == IntPtr.Zero) + { + OpenALSawmill.Error("Unable to open OpenAL device! {1}", ALC.GetError(ALDevice.Null)); + return false; + } + + // Load up ALC extensions. + var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? ""; + foreach (var extension in s.Split(' ')) + { + _alcDeviceExtensions.Add(extension); + } + return true; + } + + private void InitializeAudio() + { + OpenALSawmill = _logMan.GetSawmill("clyde.oal"); + + if (!_audioOpenDevice()) + return; + + // Create OpenAL context. + _audioCreateContext(); + + IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX"); + + _cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true); + } + + internal bool IsMainThread() + { + return Thread.CurrentThread == _gameThread; + } + + private static void RemoveEfx((int sourceHandle, int filterHandle) handles) + { + if (handles.filterHandle != 0) + EFX.DeleteFilter(handles.filterHandle); + } + + private void _checkAlcError(ALDevice device, + [CallerMemberName] string callerMember = "", + [CallerLineNumber] int callerLineNumber = -1) + { + var error = ALC.GetError(device); + if (error != AlcError.NoError) + { + OpenALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error); + } + } + + public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1) + { + var error = AL.GetError(); + if (error != ALError.NoError) + { + OpenALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error); + } + } + + private sealed class LoadedAudioSample + { + public readonly int BufferHandle; + + public LoadedAudioSample(int bufferHandle) + { + BufferHandle = bufferHandle; + } + } +} diff --git a/Robust.Client/Audio/AudioOverlay.cs b/Robust.Client/Audio/AudioOverlay.cs new file mode 100644 index 000000000..3a8098fc9 --- /dev/null +++ b/Robust.Client/Audio/AudioOverlay.cs @@ -0,0 +1,89 @@ +using System.Numerics; +using System.Text; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Client.ResourceManagement; +using Robust.Shared.Audio; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using AudioComponent = Robust.Shared.Audio.Components.AudioComponent; + +namespace Robust.Client.Audio; + +/// +/// Debug overlay for audio. +/// +public sealed class AudioOverlay : Overlay +{ + public override OverlaySpace Space => OverlaySpace.ScreenSpace; + + private IEntityManager _entManager; + private IPlayerManager _playerManager; + private AudioSystem _audio; + private SharedTransformSystem _transform; + + private Font _font; + + public AudioOverlay(IEntityManager entManager, IPlayerManager playerManager, IResourceCache cache, AudioSystem audio, SharedTransformSystem transform) + { + _entManager = entManager; + _playerManager = playerManager; + _audio = audio; + _transform = transform; + + _font = new VectorFont(cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); + } + + protected internal override void Draw(in OverlayDrawArgs args) + { + var localPlayer = _playerManager.LocalPlayer?.ControlledEntity; + + if (args.ViewportControl == null || localPlayer == null) + return; + + var screenHandle = args.ScreenHandle; + var output = new StringBuilder(); + var listenerPos = _entManager.GetComponent(localPlayer.Value).MapPosition; + + if (listenerPos.MapId != args.MapId) + return; + + var query = _entManager.AllEntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + var mapId = MapId.Nullspace; + var audioPos = Vector2.Zero; + + if (_entManager.TryGetComponent(uid, out var xform)) + { + mapId = xform.MapID; + audioPos = _transform.GetWorldPosition(uid); + } + + if (mapId != args.MapId) + continue; + + var screenPos = args.ViewportControl.WorldToScreen(audioPos); + var distance = audioPos - listenerPos.Position; + var posOcclusion = _audio.GetOcclusion(uid, listenerPos, distance, distance.Length()); + + output.Clear(); + output.AppendLine("Audio Source"); + output.AppendLine("Runtime:"); + output.AppendLine($"- Occlusion: {posOcclusion:0.0000}"); + output.AppendLine("Params:"); + output.AppendLine($"- Volume: {comp.Volume:0.0000}"); + output.AppendLine($"- Reference distance: {comp.ReferenceDistance}"); + output.AppendLine($"- Max distance: {comp.MaxDistance}"); + var outputText = output.ToString().Trim(); + var dimensions = screenHandle.GetDimensions(_font, outputText, 1f); + var buffer = new Vector2(3f, 3f); + screenHandle.DrawRect(new UIBox2(screenPos - buffer, screenPos + dimensions + buffer), new Color(39, 39, 48)); + screenHandle.DrawString(_font, screenPos, outputText); + } + } +} diff --git a/Robust.Client/Audio/AudioStream.cs b/Robust.Client/Audio/AudioStream.cs index 605e5f760..f18433a1f 100644 --- a/Robust.Client/Audio/AudioStream.cs +++ b/Robust.Client/Audio/AudioStream.cs @@ -1,18 +1,21 @@ using System; -using Robust.Client.Graphics; +using Robust.Shared.Graphics; namespace Robust.Client.Audio; +/// +/// Has the metadata for a particular audio stream as well as the relevant internal handle to it. +/// public sealed class AudioStream { public TimeSpan Length { get; } - internal ClydeHandle? ClydeHandle { get; } + internal IClydeHandle? ClydeHandle { get; } public string? Name { get; } public string? Title { get; } public string? Artist { get; } public int ChannelCount { get; } - internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null) + internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null) { ClydeHandle = handle; Length = length; diff --git a/Robust.Client/Audio/AudioSystem.Effects.cs b/Robust.Client/Audio/AudioSystem.Effects.cs new file mode 100644 index 000000000..35706e4d7 --- /dev/null +++ b/Robust.Client/Audio/AudioSystem.Effects.cs @@ -0,0 +1,76 @@ +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Client.Audio.Effects; +using Robust.Shared.Audio.Components; +using Robust.Shared.GameObjects; + +namespace Robust.Client.Audio; + +public sealed partial class AudioSystem +{ + protected override void InitializeEffect() + { + base.InitializeEffect(); + SubscribeLocalEvent(OnEffectAdd); + SubscribeLocalEvent(OnEffectShutdown); + + SubscribeLocalEvent(OnAuxiliaryAdd); + SubscribeLocalEvent(OnAuxiliaryAuto); + } + + private void OnEffectAdd(EntityUid uid, AudioEffectComponent component, ComponentAdd args) + { + var effect = new AudioEffect(_audio); + component.Effect = effect; + } + + private void OnEffectShutdown(EntityUid uid, AudioEffectComponent component, ComponentShutdown args) + { + if (component.Effect is AudioEffect effect) + { + effect.Dispose(); + } + } + + private void OnAuxiliaryAdd(EntityUid uid, AudioAuxiliaryComponent component, ComponentAdd args) + { + component.Auxiliary = new AuxiliaryAudio(); + } + + private void OnAuxiliaryAuto(EntityUid uid, AudioAuxiliaryComponent component, ref AfterAutoHandleStateEvent args) + { + if (TryComp(component.Effect, out var effectComp)) + { + component.Auxiliary.SetEffect(effectComp.Effect); + } + else + { + component.Auxiliary.SetEffect(null); + } + } + + public override void SetAuxiliary(EntityUid uid, AudioComponent audio, EntityUid? auxUid) + { + base.SetAuxiliary(uid, audio, auxUid); + if (TryComp(audio.Auxiliary, out var auxComp)) + { + audio.Source.SetAuxiliary(auxComp.Auxiliary); + } + else + { + audio.Source.SetAuxiliary(null); + } + } + + public override void SetEffect(EntityUid auxUid, AudioAuxiliaryComponent aux, EntityUid? effectUid) + { + base.SetEffect(auxUid, aux, effectUid); + if (TryComp(aux.Effect, out var effectComp)) + { + aux.Auxiliary.SetEffect(effectComp.Effect); + } + else + { + aux.Auxiliary.SetEffect(null); + } + } +} diff --git a/Robust.Client/Audio/AudioSystem.cs b/Robust.Client/Audio/AudioSystem.cs new file mode 100644 index 000000000..5e6a80e49 --- /dev/null +++ b/Robust.Client/Audio/AudioSystem.cs @@ -0,0 +1,600 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Threading.Tasks; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Components; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Exceptions; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Player; +using Robust.Shared.Replays; +using Robust.Shared.Threading; +using Robust.Shared.Utility; +using AudioComponent = Robust.Shared.Audio.Components.AudioComponent; + +namespace Robust.Client.Audio; + +public sealed partial class AudioSystem : SharedAudioSystem +{ + /* + * There's still a lot more OpenAL can do in terms of filters, auxiliary slots, etc. + * but exposing the whole thing in an easy way is a lot of effort. + */ + + [Dependency] private readonly IReplayRecordingManager _replayRecording = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IParallelManager _parMan = default!; + [Dependency] private readonly IRuntimeLog _runtimeLog = default!; + [Dependency] private readonly IAudioInternal _audio = default!; + [Dependency] private readonly SharedTransformSystem _xformSys = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + + /// + /// Per-tick cache of relevant streams. + /// + private readonly List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> _streams = new(); + private EntityUid? _listenerGrid; + + private EntityQuery _gridQuery; + private EntityQuery _physicsQuery; + private EntityQuery _xformQuery; + + private float _maxRayLength; + + /// + public override void Initialize() + { + base.Initialize(); + + UpdatesOutsidePrediction = true; + // Need to run after Eye updates so we have an accurate listener position. + UpdatesAfter.Add(typeof(EyeSystem)); + + _gridQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + _xformQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnAudioStartup); + SubscribeLocalEvent(OnAudioShutdown); + SubscribeLocalEvent(OnAudioPaused); + SubscribeLocalEvent(OnAudioState); + + // Replay stuff + SubscribeNetworkEvent(OnGlobalAudio); + SubscribeNetworkEvent(OnEntityAudio); + SubscribeNetworkEvent(OnEntityCoordinates); + + CfgManager.OnValueChanged(CVars.AudioAttenuation, OnAudioAttenuation, true); + CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true); + } + + private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args) + { + ApplyAudioParams(component.Params, component); + component.Source.Global = component.Global; + + if (TryComp(component.Auxiliary, out var auxComp)) + { + component.Source.SetAuxiliary(auxComp.Auxiliary); + } + else + { + component.Source.SetAuxiliary(null); + } + } + + /// + /// Sets the volume for the entire game. + /// + public void SetMasterVolume(float value) + { + _audio.SetMasterVolume(value); + } + + protected override void SetZOffset(float value) + { + base.SetZOffset(value); + _audio.SetZOffset(value); + } + + public override void Shutdown() + { + CfgManager.UnsubValueChanged(CVars.AudioAttenuation, OnAudioAttenuation); + CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged); + base.Shutdown(); + } + + private void OnAudioPaused(EntityUid uid, AudioComponent component, ref EntityPausedEvent args) + { + component.Pause(); + } + + protected override void OnAudioUnpaused(EntityUid uid, AudioComponent component, ref EntityUnpausedEvent args) + { + base.OnAudioUnpaused(uid, component, ref args); + component.StartPlaying(); + } + + private void OnAudioStartup(EntityUid uid, AudioComponent component, ComponentStartup args) + { + if (!Timing.ApplyingState && !Timing.IsFirstTimePredicted) + { + return; + } + + if (!TryGetAudio(component.FileName, out var audioResource)) + { + Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}"); + component.Source = new DummyAudioSource(); + return; + } + + var source = _audio.CreateAudioSource(audioResource); + + if (source == null) + { + Log.Error($"Error creating audio source for {audioResource}"); + DebugTools.Assert(false); + source = new DummyAudioSource(); + } + + // Need to set all initial data for first frame. + component.Source = source; + ApplyAudioParams(component.Params, component); + component.Global = component.Global; + // Don't play until first frame so occlusion etc. are correct. + component.Gain = 0f; + + // If audio came into range then start playback at the correct position. + var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % GetAudioLength(component.FileName).TotalSeconds; + + if (offset != 0) + { + component.PlaybackPosition = (float) offset; + } + } + + private void OnAudioShutdown(EntityUid uid, AudioComponent component, ComponentShutdown args) + { + // Breaks with prediction? + component.Source.Dispose(); + } + + private void OnAudioAttenuation(int obj) + { + _audio.SetAttenuation((Attenuation) obj); + } + + private void OnRaycastLengthChanged(float value) + { + _maxRayLength = value; + } + + public override void FrameUpdate(float frameTime) + { + var eye = _eyeManager.CurrentEye; + _audio.SetRotation(eye.Rotation); + _audio.SetPosition(eye.Position.Position); + + var ourPos = eye.Position; + var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount }; + + var query = AllEntityQuery(); + _streams.Clear(); + + while (query.MoveNext(out var uid, out var comp, out var xform)) + { + _streams.Add((uid, comp, xform)); + } + + _mapManager.TryFindGridAt(ourPos, out var gridUid, out _); + _listenerGrid = gridUid == EntityUid.Invalid ? null : gridUid; + + try + { + Parallel.ForEach(_streams, opts, comp => ProcessStream(comp.Entity, comp.Component, comp.Xform, ourPos)); + } + catch (Exception e) + { + Log.Error($"Caught exception while processing entity streams."); + _runtimeLog.LogException(e, $"{nameof(AudioSystem)}.{nameof(FrameUpdate)}"); + } + } + + private void ProcessStream(EntityUid entity, AudioComponent component, TransformComponent xform, MapCoordinates listener) + { + // TODO: + // I Originally tried to be fancier here but it caused audio issues so just trying + // to replicate the old behaviour for now. + if (!component.Started) + { + component.Started = true; + component.StartPlaying(); + } + + // If it's global but on another map (that isn't nullspace) then stop playing it. + if (component.Global) + { + if (xform.MapID != MapId.Nullspace && listener.MapId != xform.MapID) + { + component.Gain = 0f; + return; + } + + // Resume playing. + component.Volume = component.Params.Volume; + return; + } + + // Non-global sounds, stop playing if on another map. + // Not relevant to us. + if (listener.MapId != xform.MapID) + { + component.Gain = 0f; + return; + } + + Vector2 worldPos; + var gridUid = xform.ParentUid; + + // Handle grid audio differently by using nearest-edge instead of entity centre. + if (_gridQuery.HasComponent(gridUid)) + { + // It's our grid so max volume. + if (_listenerGrid == gridUid) + { + component.Volume = component.Params.Volume; + component.Occlusion = 0f; + component.Position = listener.Position; + return; + } + + // TODO: Need a grid-optimised version because this is gonna be expensive. + // Just to avoid clipping on and off grid or nearestPoint changing we'll + // always set the sound to listener's pos, we'll just manually do gain ourselves. + if (_physics.TryGetNearest(gridUid, listener, out _, out var gridDistance)) + { + // Out of range + if (gridDistance > component.MaxDistance) + { + component.Gain = 0f; + return; + } + + var paramsGain = MathF.Pow(10, component.Params.Volume / 10); + + // Thought I'd never have to manually calculate gain again but this is the least + // unpleasant audio I could get at the moment. + component.Gain = paramsGain * _audio.GetAttenuationGain( + gridDistance, + component.Params.RolloffFactor, + component.Params.ReferenceDistance, + component.Params.MaxDistance); + component.Position = listener.Position; + return; + } + + // Can't get nearest point so don't play anymore. + component.Gain = 0f; + return; + } + + worldPos = _xformSys.GetWorldPosition(entity); + component.Volume = component.Params.Volume; + + // Max distance check + var delta = worldPos - listener.Position; + var distance = delta.Length(); + + // Out of range so just clip it for us. + if (distance > component.MaxDistance) + { + // Still keeps the source playing, just with no volume. + component.Gain = 0f; + return; + } + + // Update audio occlusion + var occlusion = GetOcclusion(entity, listener, delta, distance); + component.Occlusion = occlusion; + + // Update audio positions. + component.Position = worldPos; + + // Make race cars go NYYEEOOOOOMMMMM + if (_physicsQuery.TryGetComponent(entity, out var physicsComp)) + { + // This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit + // inefficient. + var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform, _xformQuery, _physicsQuery); + component.Velocity = velocity; + } + } + + internal float GetOcclusion(EntityUid entity, MapCoordinates listener, Vector2 delta, float distance) + { + float occlusion = 0; + + if (distance > 0.1) + { + var rayLength = MathF.Min(distance, _maxRayLength); + var ray = new CollisionRay(listener.Position, delta / distance, OcclusionCollisionMask); + occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, entity); + } + + return occlusion; + } + + private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio) + { + if (_resourceCache.TryGetResource(new ResPath(filename), out audio)) + return true; + + Log.Error($"Server tried to play audio file {filename} which does not exist."); + return false; + } + + private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IAudioSource? source) + { + if (!Timing.IsFirstTimePredicted) + { + source = null; + Log.Error($"Tried to create audio source outside of prediction!"); + DebugTools.Assert(false); + return false; + } + + source = _audio.CreateAudioSource(stream); + return source != null; + } + + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates, + AudioParams? audioParams = null) + { + return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams); + } + + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null) + { + return PlayEntity(filename, Filter.Local(), uid, true, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null) + { + if (Timing.IsFirstTimePredicted || sound == null) + return PlayEntity(sound, Filter.Local(), source, false, audioParams); + + return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio.... + } + + public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null) + { + if (Timing.IsFirstTimePredicted || sound == null) + return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams); + + return null; + } + + /// + /// Play an audio file globally, without position. + /// + /// The resource path to the OGG Vorbis file to play. + /// + private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, AudioParams? audioParams = null, bool recordReplay = true) + { + if (recordReplay && _replayRecording.IsRecording) + { + _replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage + { + FileName = filename, + AudioParams = audioParams ?? AudioParams.Default + }); + } + + return TryGetAudio(filename, out var audio) ? PlayGlobal(audio, audioParams) : default; + } + + /// + /// Play an audio stream globally, without position. + /// + /// The audio stream to play. + /// + private (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null) + { + var (entity, component) = CreateAndStartPlayingStream(audioParams, stream); + component.Global = true; + component.Source.Global = true; + Dirty(entity, component); + return (entity, component); + } + + /// + /// Play an audio file following an entity. + /// + /// The resource path to the OGG Vorbis file to play. + /// The entity "emitting" the audio. + private (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true) + { + if (recordReplay && _replayRecording.IsRecording) + { + _replayRecording.RecordReplayMessage(new PlayAudioEntityMessage + { + FileName = filename, + NetEntity = GetNetEntity(entity), + AudioParams = audioParams ?? AudioParams.Default + }); + } + + return TryGetAudio(filename, out var audio) ? PlayEntity(audio, entity, audioParams) : default; + } + + /// + /// Play an audio stream following an entity. + /// + /// The audio stream to play. + /// The entity "emitting" the audio. + /// + private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null) + { + var playing = CreateAndStartPlayingStream(audioParams, stream); + _xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero)); + + return playing; + } + + /// + /// Play an audio file at a static position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The coordinates at which to play the audio. + /// + private (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true) + { + if (recordReplay && _replayRecording.IsRecording) + { + _replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage + { + FileName = filename, + Coordinates = GetNetCoordinates(coordinates), + AudioParams = audioParams ?? AudioParams.Default + }); + } + + return TryGetAudio(filename, out var audio) ? PlayStatic(audio, coordinates, audioParams) : default; + } + + /// + /// Play an audio stream at a static position. + /// + /// The audio stream to play. + /// The coordinates at which to play the audio. + /// + private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + var playing = CreateAndStartPlayingStream(audioParams, stream); + _xformSys.SetCoordinates(playing.Entity, coordinates); + return playing; + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + { + return PlayGlobal(filename, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null) + { + return PlayEntity(filename, entity, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + { + return PlayStatic(filename, coordinates, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null) + { + return PlayGlobal(filename, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null) + { + return PlayGlobal(filename, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + { + return PlayEntity(filename, uid, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + { + return PlayEntity(filename, uid, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + return PlayStatic(filename, coordinates, audioParams); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + return PlayStatic(filename, coordinates, audioParams); + } + + private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream) + { + var audioP = audioParams ?? AudioParams.Default; + var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace); + var comp = SetupAudio(entity, stream.Name!, audioP); + EntityManager.InitializeAndStartEntity(entity); + var source = comp.Source; + + // TODO clamp the offset inside of SetPlaybackPosition() itself. + var offset = audioP.PlayOffsetSeconds; + offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f); + source.PlaybackPosition = offset; + + ApplyAudioParams(audioP, comp); + comp.Params = audioP; + source.StartPlaying(); + return (entity, comp); + } + + /// + /// Applies the audioparams to the underlying audio source. + /// + private void ApplyAudioParams(AudioParams audioParams, IAudioSource source) + { + source.Pitch = audioParams.Pitch; + source.Volume = audioParams.Volume; + source.RolloffFactor = audioParams.RolloffFactor; + source.MaxDistance = audioParams.MaxDistance; + source.ReferenceDistance = audioParams.ReferenceDistance; + source.Looping = audioParams.Loop; + } + + private void OnEntityCoordinates(PlayAudioPositionalMessage ev) + { + PlayStatic(ev.FileName, GetCoordinates(ev.Coordinates), ev.AudioParams, false); + } + + private void OnEntityAudio(PlayAudioEntityMessage ev) + { + PlayEntity(ev.FileName, GetEntity(ev.NetEntity), ev.AudioParams, false); + } + + private void OnGlobalAudio(PlayAudioGlobalMessage ev) + { + PlayGlobal(ev.FileName, ev.AudioParams, false); + } + + protected override TimeSpan GetAudioLengthImpl(string filename) + { + return _resourceCache.GetResource(filename).AudioStream.Length; + } +} diff --git a/Robust.Client/Audio/Effects/AudioEffect.cs b/Robust.Client/Audio/Effects/AudioEffect.cs new file mode 100644 index 000000000..8e563ddab --- /dev/null +++ b/Robust.Client/Audio/Effects/AudioEffect.cs @@ -0,0 +1,455 @@ +using System; +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Effects; +using Robust.Shared.Maths; + +namespace Robust.Client.Audio.Effects; + +/// +internal sealed class AudioEffect : IAudioEffect +{ + internal int Handle; + + private readonly IAudioInternal _master; + + public AudioEffect(IAudioInternal manager) + { + Handle = EFX.GenEffect(); + _master = manager; + EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb); + } + + public void Dispose() + { + if (Handle != 0) + { + EFX.DeleteEffect(Handle); + Handle = 0; + } + } + + private void _checkDisposed() + { + if (Handle == -1) + { + throw new ObjectDisposedException(nameof(AudioEffect)); + } + } + + /// + public float Density + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value); + _master._checkAlError(); + } + } + + /// + public float Diffusion + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value); + _master._checkAlError(); + } + } + + /// + public float Gain + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbGain, value); + _master._checkAlError(); + } + } + + /// + public float GainHF + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value); + _master._checkAlError(); + } + } + + /// + public float GainLF + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value); + _master._checkAlError(); + } + } + + /// + public float DecayTime + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value); + _master._checkAlError(); + } + } + + /// + public float DecayHFRatio + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value); + _master._checkAlError(); + } + } + + /// + public float DecayLFRatio + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value); + _master._checkAlError(); + } + } + + /// + public float ReflectionsGain + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value); + _master._checkAlError(); + } + } + + /// + public float ReflectionsDelay + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value); + _master._checkAlError(); + } + } + + /// + public Vector3 ReflectionsPan + { + get + { + _checkDisposed(); + var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan); + _master._checkAlError(); + return new Vector3(value.X, value.Z, value.Y); + } + set + { + _checkDisposed(); + var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z); + EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec); + _master._checkAlError(); + } + } + + /// + public float LateReverbGain + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value); + _master._checkAlError(); + } + } + + /// + public float LateReverbDelay + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value); + _master._checkAlError(); + } + } + + /// + public Vector3 LateReverbPan + { + get + { + _checkDisposed(); + var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan); + _master._checkAlError(); + return new Vector3(value.X, value.Z, value.Y); + } + set + { + _checkDisposed(); + var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z); + EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec); + _master._checkAlError(); + } + } + + /// + public float EchoTime + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value); + _master._checkAlError(); + } + } + + /// + public float EchoDepth + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value); + _master._checkAlError(); + } + } + + /// + public float ModulationTime + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value); + _master._checkAlError(); + } + } + + /// + public float ModulationDepth + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value); + _master._checkAlError(); + } + } + + /// + public float AirAbsorptionGainHF + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value); + _master._checkAlError(); + } + } + + /// + public float HFReference + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value); + _master._checkAlError(); + } + } + + /// + public float LFReference + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value); + _master._checkAlError(); + } + } + + /// + public float RoomRolloffFactor + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value); + _master._checkAlError(); + } + } + + /// + public int DecayHFLimit + { + get + { + _checkDisposed(); + EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value); + _master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value); + _master._checkAlError(); + } + } +} diff --git a/Robust.Client/Audio/Effects/AuxiliaryAudio.cs b/Robust.Client/Audio/Effects/AuxiliaryAudio.cs new file mode 100644 index 000000000..a638a390b --- /dev/null +++ b/Robust.Client/Audio/Effects/AuxiliaryAudio.cs @@ -0,0 +1,32 @@ +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Shared.Audio.Effects; + +namespace Robust.Client.Audio.Effects; + +/// +internal sealed class AuxiliaryAudio : IAuxiliaryAudio +{ + internal int Handle = EFX.GenAuxiliaryEffectSlot(); + + public void Dispose() + { + if (Handle != -1) + { + EFX.DeleteAuxiliaryEffectSlot(Handle); + Handle = -1; + } + } + + /// + public void SetEffect(IAudioEffect? effect) + { + if (effect is AudioEffect audEffect) + { + EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle); + } + else + { + EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0); + } + } +} diff --git a/Robust.Client/Audio/HeadlessAudioManager.cs b/Robust.Client/Audio/HeadlessAudioManager.cs new file mode 100644 index 000000000..4d11befc7 --- /dev/null +++ b/Robust.Client/Audio/HeadlessAudioManager.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Numerics; +using Robust.Shared.Audio; +using Robust.Shared.Audio.AudioLoading; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Maths; + +namespace Robust.Client.Audio; + +/// +/// Headless client audio. +/// +internal sealed class HeadlessAudioManager : IAudioInternal +{ + /// + public void InitializePostWindowing() + { + } + + /// + public void Shutdown() + { + } + + /// + public void FlushALDisposeQueues() + { + } + + /// + public IAudioSource CreateAudioSource(AudioStream stream) + { + return DummyAudioSource.Instance; + } + + /// + public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false) + { + return DummyBufferedAudioSource.Instance; + } + + /// + public void SetPosition(Vector2 position) + { + } + + /// + public void SetRotation(Angle angle) + { + } + + /// + public void SetMasterVolume(float value) + { + } + + /// + public void SetAttenuation(Attenuation attenuation) + { + } + + /// + public void StopAllAudio() + { + } + + /// + public void SetZOffset(float f) + { + } + + /// + public void _checkAlError(string callerMember = "", int callerLineNumber = -1) + { + } + + /// + public float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance) + { + return 0f; + } + + public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null) + { + var metadata = AudioLoaderOgg.LoadAudioMetadata(stream); + return AudioStreamFromMetadata(metadata, name); + } + + public AudioStream LoadAudioWav(Stream stream, string? name = null) + { + var metadata = AudioLoaderWav.LoadAudioMetadata(stream); + return AudioStreamFromMetadata(metadata, name); + } + + public AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null) + { + var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate); + return new AudioStream(null, length, channels, name); + } + + private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name) + { + return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist); + } +} diff --git a/Robust.Client/Audio/IAudioInternal.cs b/Robust.Client/Audio/IAudioInternal.cs new file mode 100644 index 000000000..cc4ce7753 --- /dev/null +++ b/Robust.Client/Audio/IAudioInternal.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Maths; + +namespace Robust.Client.Audio; + +/// +/// Handles clientside audio. +/// +internal interface IAudioInternal +{ + void InitializePostWindowing(); + void Shutdown(); + + /// + /// Flushes all pending queues for disposing of AL sources. + /// + void FlushALDisposeQueues(); + + IAudioSource? CreateAudioSource(AudioStream stream); + + IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false); + + /// + /// Sets position for the audio listener. + /// + void SetPosition(Vector2 position); + + /// + /// Sets rotation for the audio listener. + /// + void SetRotation(Angle angle); + + void SetMasterVolume(float value); + void SetAttenuation(Attenuation attenuation); + + /// + /// Stops all audio from playing. + /// + void StopAllAudio(); + + /// + /// Sets the Z-offset for the audio listener. + /// + void SetZOffset(float f); + + void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1); + + /// + /// Manually calculates the specified gain for an attenuation source with the specified distance. + /// + float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance); + + AudioStream LoadAudioOggVorbis(Stream stream, string? name = null); + + AudioStream LoadAudioWav(Stream stream, string? name = null); + + AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null); +} diff --git a/Robust.Client/Audio/Midi/IMidiRenderer.cs b/Robust.Client/Audio/Midi/IMidiRenderer.cs index 96cf7a2f4..216fe2c09 100644 --- a/Robust.Client/Audio/Midi/IMidiRenderer.cs +++ b/Robust.Client/Audio/Midi/IMidiRenderer.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using Robust.Client.Graphics; using Robust.Shared.Audio.Midi; +using Robust.Shared.Audio.Sources; using Robust.Shared.GameObjects; using Robust.Shared.Map; @@ -20,7 +21,7 @@ public interface IMidiRenderer : IDisposable /// /// The buffered audio source of this renderer. /// - internal IClydeBufferedAudioSource Source { get; } + internal IBufferedAudioSource Source { get; } /// /// Whether this renderer has been disposed or not. diff --git a/Robust.Client/Audio/Midi/MidiManager.cs b/Robust.Client/Audio/Midi/MidiManager.cs index 19d827aed..a3b036346 100644 --- a/Robust.Client/Audio/Midi/MidiManager.cs +++ b/Robust.Client/Audio/Midi/MidiManager.cs @@ -10,6 +10,7 @@ using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared; using Robust.Shared.Asynchronous; +using Robust.Shared.Audio; using Robust.Shared.Audio.Midi; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; @@ -41,10 +42,10 @@ internal sealed partial class MidiManager : IMidiManager [ViewVariables] private TimeSpan _nextPositionUpdate = TimeSpan.Zero; [Dependency] private readonly IEyeManager _eyeManager = default!; - [Dependency] private readonly IResourceCacheInternal _resourceManager = default!; + [Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IConfigurationManager _cfgMan = default!; - [Dependency] private readonly IClydeAudio _clydeAudio = default!; + [Dependency] private readonly IAudioInternal _audio = default!; [Dependency] private readonly ITaskManager _taskManager = default!; [Dependency] private readonly ILogManager _logger = default!; [Dependency] private readonly IParallelManager _parallel = default!; @@ -273,7 +274,7 @@ internal sealed partial class MidiManager : IMidiManager { soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks); - var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill); + var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill); _midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}"); // Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont. @@ -351,7 +352,7 @@ internal sealed partial class MidiManager : IMidiManager renderer.LoadSoundfont(file.ToString()); } - renderer.Source.SetVolume(Volume); + renderer.Source.Volume = _volume; lock (_renderers) { @@ -374,6 +375,7 @@ internal sealed partial class MidiManager : IMidiManager // Update positions of streams every frame. // This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow. + // so TRUE lock (_renderers) { @@ -415,11 +417,13 @@ internal sealed partial class MidiManager : IMidiManager return; if (_volumeDirty) - renderer.Source.SetVolume(Volume); + { + renderer.Source.Volume = Volume; + } if (!renderer.Mono) { - renderer.Source.SetGlobal(); + renderer.Source.Global = true; return; } @@ -434,14 +438,19 @@ internal sealed partial class MidiManager : IMidiManager return; } - if (!renderer.Source.SetPosition(renderer.TrackingCoordinates.Value.Position)) + var position = renderer.TrackingCoordinates.Value; + + if (position.MapId == MapId.Nullspace) { return; } + renderer.Source.Position = position.Position; + var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value, xformQuery: transQuery, physicsQuery: physicsQuery); - renderer.Source.SetVelocity(vel); + + renderer.Source.Velocity = vel; } if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap) @@ -465,11 +474,11 @@ internal sealed partial class MidiManager : IMidiManager renderer.TrackingEntity); } - renderer.Source.SetOcclusion(occlusion); + renderer.Source.Occlusion = occlusion; } else { - renderer.Source.SetOcclusion(float.MaxValue); + renderer.Source.Occlusion = float.MaxValue; } } diff --git a/Robust.Client/Audio/Midi/MidiRenderer.cs b/Robust.Client/Audio/Midi/MidiRenderer.cs index 9d14bb081..ec0c16202 100644 --- a/Robust.Client/Audio/Midi/MidiRenderer.cs +++ b/Robust.Client/Audio/Midi/MidiRenderer.cs @@ -4,7 +4,9 @@ using JetBrains.Annotations; using NFluidsynth; using Robust.Client.Graphics; using Robust.Shared.Asynchronous; +using Robust.Shared.Audio; using Robust.Shared.Audio.Midi; +using Robust.Shared.Audio.Sources; using Robust.Shared.GameObjects; using Robust.Shared.Log; using Robust.Shared.Map; @@ -52,8 +54,8 @@ internal sealed class MidiRenderer : IMidiRenderer private IMidiRenderer? _master; public MidiRendererState RendererState => _rendererState; - public IClydeBufferedAudioSource Source { get; set; } - IClydeBufferedAudioSource IMidiRenderer.Source => Source; + public IBufferedAudioSource Source { get; set; } + IBufferedAudioSource IMidiRenderer.Source => Source; [ViewVariables] public bool Disposed { get; private set; } = false; @@ -247,7 +249,7 @@ internal sealed class MidiRenderer : IMidiRenderer public event Action? OnMidiPlayerFinished; internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono, - IMidiManager midiManager, IClydeAudio clydeAudio, ITaskManager taskManager, ISawmill midiSawmill) + IMidiManager midiManager, IAudioInternal clydeAudio, ITaskManager taskManager, ISawmill midiSawmill) { _midiManager = midiManager; _taskManager = taskManager; @@ -488,7 +490,7 @@ internal sealed class MidiRenderer : IMidiRenderer } } - if (!Source.IsPlaying) Source.StartPlaying(); + Source.StartPlaying(); } public void ApplyState(MidiRendererState state, bool filterChannels = false) diff --git a/Robust.Client/Audio/ShowAudioCommand.cs b/Robust.Client/Audio/ShowAudioCommand.cs new file mode 100644 index 000000000..ed2a19800 --- /dev/null +++ b/Robust.Client/Audio/ShowAudioCommand.cs @@ -0,0 +1,34 @@ +using Robust.Client.Audio; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Client.ResourceManagement; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Robust.Client.Commands; + +/// +/// Shows a debug overlay for audio sources. +/// +public sealed class ShowAudioCommand : LocalizedCommands +{ + [Dependency] private readonly IResourceCache _client = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IOverlayManager _overlayManager = default!; + [Dependency] private readonly IPlayerManager _playerMgr = default!; + public override string Command => "showaudio"; + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (_overlayManager.HasOverlay()) + _overlayManager.RemoveOverlay(); + else + _overlayManager.AddOverlay(new AudioOverlay( + _entManager, + _playerMgr, + _client, + _entManager.System(), + _entManager.System())); + } +} diff --git a/Robust.Client/Audio/Sources/AudioSource.cs b/Robust.Client/Audio/Sources/AudioSource.cs new file mode 100644 index 000000000..0558f29f3 --- /dev/null +++ b/Robust.Client/Audio/Sources/AudioSource.cs @@ -0,0 +1,90 @@ +using System; +using System.Numerics; +using OpenTK.Audio.OpenAL; +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Shared.Audio; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Robust.Client.Audio.Sources; + +internal sealed class AudioSource : BaseAudioSource +{ + /// + /// Underlying stream to the audio. + /// + private readonly AudioStream _sourceStream; + +#if DEBUG + private bool _didPositionWarning; +#endif + + public AudioSource(AudioManager master, int sourceHandle, AudioStream sourceStream) : base(master, sourceHandle) + { + _sourceStream = sourceStream; + } + + /// + public override Vector2 Position + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSource3f.Position, out var x, out var y, out _); + Master._checkAlError(); + return new Vector2(x, y); + } + set + { + _checkDisposed(); + + var (x, y) = value; + + if (!AreFinite(x, y)) + { + return; + } +#if DEBUG + // OpenAL doesn't seem to want to play stereo positionally. + // Log a warning if people try to. + if (_sourceStream.ChannelCount > 1 && !_didPositionWarning) + { + _didPositionWarning = true; + Master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.", + _sourceStream.Name); + // warning isn't enough, people just ignore it :( + DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo."); + } +#endif + + AL.Source(SourceHandle, ALSource3f.Position, x, y, 0); + Master._checkAlError(); + } + } + + ~AudioSource() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + if (!disposing) + { + // We can't run this code inside the finalizer thread so tell Clyde to clear it up later. + Master.DeleteSourceOnMainThread(SourceHandle, FilterHandle); + } + else + { + if (FilterHandle != 0) + EFX.DeleteFilter(FilterHandle); + + AL.DeleteSource(SourceHandle); + Master.RemoveAudioSource(SourceHandle); + Master._checkAlError(); + } + + FilterHandle = 0; + SourceHandle = -1; + } +} diff --git a/Robust.Client/Audio/Sources/BaseAudioSource.cs b/Robust.Client/Audio/Sources/BaseAudioSource.cs new file mode 100644 index 000000000..38c9fdb3e --- /dev/null +++ b/Robust.Client/Audio/Sources/BaseAudioSource.cs @@ -0,0 +1,390 @@ +using System; +using System.Numerics; +using OpenTK.Audio.OpenAL; +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Client.Audio.Effects; +using Robust.Shared.Audio.Effects; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Maths; + +namespace Robust.Client.Audio.Sources; + +internal abstract class BaseAudioSource : IAudioSource +{ + /* + * This may look weird having all these methods here however + * we need to handle disposing plus checking for errors hence we get this. + */ + + /// + /// Handle to the AL source. + /// + protected int SourceHandle; + + /// + /// Source to the EFX filter if applicable. + /// + protected int FilterHandle; + + protected readonly AudioManager Master; + + /// + /// Prior gain that was set. + /// + private float _gain; + + private bool IsEfxSupported => Master.IsEfxSupported; + + protected BaseAudioSource(AudioManager master, int sourceHandle) + { + Master = master; + SourceHandle = sourceHandle; + AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain); + } + + public void Pause() + { + AL.SourcePause(SourceHandle); + } + + /// + public void StartPlaying() + { + if (Playing) + return; + + Playing = true; + } + + /// + public void StopPlaying() + { + if (!Playing) + return; + + Playing = false; + } + + /// + public virtual bool Playing + { + get + { + _checkDisposed(); + var state = AL.GetSourceState(SourceHandle); + Master._checkAlError(); + return state == ALSourceState.Playing; + } + set + { + _checkDisposed(); + + if (value) + { + AL.SourcePlay(SourceHandle); + } + else + { + AL.SourceStop(SourceHandle); + } + + + Master._checkAlError(); + } + } + + /// + public bool Looping + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret); + Master._checkAlError(); + return ret; + } + set + { + _checkDisposed(); + AL.Source(SourceHandle, ALSourceb.Looping, value); + Master._checkAlError(); + } + } + + /// + public bool Global + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourceb.SourceRelative, out var value); + Master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + AL.Source(SourceHandle, ALSourceb.SourceRelative, value); + Master._checkAlError(); + } + } + + /// + public virtual Vector2 Position + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSource3f.Position, out var x, out var y, out _); + Master._checkAlError(); + return new Vector2(x, y); + } + set + { + _checkDisposed(); + + var (x, y) = value; + + if (!AreFinite(x, y)) + { + return; + } + + AL.Source(SourceHandle, ALSource3f.Position, x, y, 0); + Master._checkAlError(); + } + } + + /// + public float Pitch { get; set; } + + /// + public float Volume + { + get + { + var gain = Gain; + var volume = 10f * MathF.Log10(gain); + return volume; + } + set => Gain = MathF.Pow(10, value / 10); + } + + /// + public float Gain + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourcef.Gain, out var gain); + Master._checkAlError(); + return gain; + } + set + { + _checkDisposed(); + var priorOcclusion = 1f; + if (!IsEfxSupported) + { + AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain); + priorOcclusion = priorGain / _gain; + } + + _gain = value; + AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion); + Master._checkAlError(); + } + } + + /// + public float MaxDistance + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value); + Master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + AL.Source(SourceHandle, ALSourcef.MaxDistance, value); + Master._checkAlError(); + } + } + + /// + public float RolloffFactor + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourcef.RolloffFactor, out var value); + Master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + AL.Source(SourceHandle, ALSourcef.RolloffFactor, value); + Master._checkAlError(); + } + } + + /// + public float ReferenceDistance + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourcef.ReferenceDistance, out var value); + Master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value); + Master._checkAlError(); + } + } + + /// + public float Occlusion + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value); + Master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + var cutoff = MathF.Exp(-value * 1); + var gain = MathF.Pow(cutoff, 0.1f); + if (IsEfxSupported) + { + SetOcclusionEfx(gain, cutoff); + } + else + { + gain *= gain * gain; + AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain); + } + Master._checkAlError(); + } + } + + /// + public float PlaybackPosition + { + get + { + _checkDisposed(); + AL.GetSource(SourceHandle, ALSourcef.SecOffset, out var value); + Master._checkAlError(); + return value; + } + set + { + _checkDisposed(); + AL.Source(SourceHandle, ALSourcef.SecOffset, value); + Master._checkAlError(); + } + } + + /// + public Vector2 Velocity + { + get + { + _checkDisposed(); + + AL.GetSource(SourceHandle, ALSource3f.Velocity, out var x, out var y, out _); + Master._checkAlError(); + return new Vector2(x, y); + } + set + { + _checkDisposed(); + + var (x, y) = value; + + if (!AreFinite(x, y)) + { + return; + } + + AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0); + Master._checkAlError(); + } + } + + public void SetAuxiliary(IAuxiliaryAudio? audio) + { + _checkDisposed(); + + if (audio is AuxiliaryAudio impAudio) + { + EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0); + } + else + { + EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0); + } + + Master._checkAlError(); + } + + private void SetOcclusionEfx(float gain, float cutoff) + { + if (FilterHandle == 0) + { + FilterHandle = EFX.GenFilter(); + EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass); + } + + EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain); + EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff); + AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle); + } + + protected static bool AreFinite(float x, float y) + { + if (float.IsFinite(x) && float.IsFinite(y)) + { + return true; + } + + return false; + } + + ~BaseAudioSource() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected abstract void Dispose(bool disposing); + + protected bool _isDisposed() + { + return SourceHandle == -1; + } + + protected void _checkDisposed() + { + if (SourceHandle == -1) + { + throw new ObjectDisposedException(nameof(BaseAudioSource)); + } + } +} diff --git a/Robust.Client/Audio/Sources/BufferedAudioSource.cs b/Robust.Client/Audio/Sources/BufferedAudioSource.cs new file mode 100644 index 000000000..f646bd556 --- /dev/null +++ b/Robust.Client/Audio/Sources/BufferedAudioSource.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using OpenTK.Audio.OpenAL; +using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; +using Robust.Client.Graphics; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Maths; + +namespace Robust.Client.Audio.Sources; + +internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSource +{ + private int? SourceHandle = null; + private int[] BufferHandles; + private Dictionary BufferMap = new(); + private readonly AudioManager _master; + private bool _mono = true; + private bool _float = false; + private int FilterHandle; + + private float _gain; + + public int SampleRate { get; set; } = 44100; + + private bool IsEfxSupported => _master.IsEfxSupported; + + public BufferedAudioSource(AudioManager master, int sourceHandle, int[] bufferHandles, bool floatAudio = false) : base(master, sourceHandle) + { + _master = master; + SourceHandle = sourceHandle; + BufferHandles = bufferHandles; + for (int i = 0; i < BufferHandles.Length; i++) + { + var bufferHandle = BufferHandles[i]; + BufferMap[bufferHandle] = i; + } + _float = floatAudio; + AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain); + } + + /// + public override bool Playing + { + get + { + _checkDisposed(); + var state = AL.GetSourceState(SourceHandle!.Value); + _master._checkAlError(); + return state == ALSourceState.Playing; + } + set + { + if (value) + { + _checkDisposed(); + // IDK why this stackallocs but gonna leave it for now. + AL.SourcePlay(stackalloc int[] {SourceHandle!.Value}); + _master._checkAlError(); + } + else + { + if (_isDisposed()) + return; + + AL.SourceStop(SourceHandle!.Value); + _master._checkAlError(); + } + } + } + + ~BufferedAudioSource() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + if (SourceHandle == null) + return; + + if (!_master.IsMainThread()) + { + // We can't run this code inside another thread so tell Clyde to clear it up later. + _master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle); + + foreach (var handle in BufferHandles) + { + _master.DeleteAudioBufferOnMainThread(handle); + } + } + else + { + if (FilterHandle != 0) + EFX.DeleteFilter(FilterHandle); + + AL.DeleteSource(SourceHandle.Value); + AL.DeleteBuffers(BufferHandles); + _master.RemoveBufferedAudioSource(SourceHandle.Value); + _master._checkAlError(); + } + + FilterHandle = 0; + SourceHandle = null; + } + + public int GetNumberOfBuffersProcessed() + { + _checkDisposed(); + // ReSharper disable once PossibleInvalidOperationException + AL.GetSource(SourceHandle!.Value, ALGetSourcei.BuffersProcessed, out var buffersProcessed); + return buffersProcessed; + } + + public unsafe void GetBuffersProcessed(Span handles) + { + _checkDisposed(); + var entries = Math.Min(Math.Min(handles.Length, BufferHandles.Length), GetNumberOfBuffersProcessed()); + fixed (int* ptr = handles) + { + AL.SourceUnqueueBuffers(SourceHandle!.Value, entries, ptr); + } + + for (var i = 0; i < entries; i++) + { + handles[i] = BufferMap[handles[i]]; + } + } + + public unsafe void WriteBuffer(int handle, ReadOnlySpan data) + { + _checkDisposed(); + + if(_float) + throw new InvalidOperationException("Can't write ushort numbers to buffers when buffer type is float!"); + + if (handle >= BufferHandles.Length) + { + throw new ArgumentOutOfRangeException(nameof(handle), + $"Got {handle}. Expected less than {BufferHandles.Length}"); + } + + fixed (ushort* ptr = data) + { + AL.BufferData(BufferHandles[handle], _mono ? ALFormat.Mono16 : ALFormat.Stereo16, (IntPtr) ptr, + _mono ? data.Length / 2 * sizeof(ushort) : data.Length * sizeof(ushort), SampleRate); + } + } + + public unsafe void WriteBuffer(int handle, ReadOnlySpan data) + { + _checkDisposed(); + + if(!_float) + throw new InvalidOperationException("Can't write float numbers to buffers when buffer type is ushort!"); + + if (handle >= BufferHandles.Length) + { + throw new ArgumentOutOfRangeException(nameof(handle), + $"Got {handle}. Expected less than {BufferHandles.Length}"); + } + + fixed (float* ptr = data) + { + AL.BufferData(BufferHandles[handle], _mono ? ALFormat.MonoFloat32Ext : ALFormat.StereoFloat32Ext, (IntPtr) ptr, + _mono ? data.Length / 2 * sizeof(float) : data.Length * sizeof(float), SampleRate); + } + } + + public unsafe void QueueBuffers(ReadOnlySpan handles) + { + _checkDisposed(); + + Span realHandles = stackalloc int[handles.Length]; + handles.CopyTo(realHandles); + + for (var i = 0; i < realHandles.Length; i++) + { + var handle = realHandles[i]; + if (handle >= BufferHandles.Length) + throw new ArgumentOutOfRangeException(nameof(handles), $"Invalid handle with index {i}!"); + realHandles[i] = BufferHandles[handle]; + } + + fixed (int* ptr = realHandles) + // ReSharper disable once PossibleInvalidOperationException + { + AL.SourceQueueBuffers(SourceHandle!.Value, handles.Length, ptr); + } + } + + public unsafe void EmptyBuffers() + { + _checkDisposed(); + var length = SampleRate / BufferHandles.Length * (_mono ? 1 : 2); + + Span handles = stackalloc int[BufferHandles.Length]; + + if (_float) + { + var empty = new float[length]; + var span = (Span) empty; + + for (var i = 0; i < BufferHandles.Length; i++) + { + WriteBuffer(BufferMap[BufferHandles[i]], span); + handles[i] = BufferMap[BufferHandles[i]]; + } + } + else + { + var empty = new ushort[length]; + var span = (Span) empty; + + for (var i = 0; i < BufferHandles.Length; i++) + { + WriteBuffer(BufferMap[BufferHandles[i]], span); + handles[i] = BufferMap[BufferHandles[i]]; + } + } + + QueueBuffers(handles); + } +} diff --git a/Robust.Client/ClientIoC.cs b/Robust.Client/ClientIoC.cs index 4e6f377d1..8bb644d98 100644 --- a/Robust.Client/ClientIoC.cs +++ b/Robust.Client/ClientIoC.cs @@ -1,4 +1,5 @@ using System; +using Robust.Client.Audio; using Robust.Client.Audio.Midi; using Robust.Client.Configuration; using Robust.Client.Console; @@ -6,7 +7,6 @@ using Robust.Client.Debugging; using Robust.Client.GameObjects; using Robust.Client.GameStates; using Robust.Client.Graphics; -using Robust.Client.Graphics.Audio; using Robust.Client.Graphics.Clyde; using Robust.Client.Input; using Robust.Client.Map; @@ -107,8 +107,7 @@ namespace Robust.Client deps.Register(); deps.Register(); deps.Register(); - deps.Register(); - deps.Register(); + deps.Register(); deps.Register(); deps.Register(); deps.Register(); @@ -117,8 +116,7 @@ namespace Robust.Client deps.Register(); deps.Register(); deps.Register(); - deps.Register(); - deps.Register(); + deps.Register(); deps.Register(); deps.Register(); deps.Register(); diff --git a/Robust.Client/Console/Commands/Debug.cs b/Robust.Client/Console/Commands/Debug.cs index dcf0ef66d..592c89704 100644 --- a/Robust.Client/Console/Commands/Debug.cs +++ b/Robust.Client/Console/Commands/Debug.cs @@ -15,6 +15,7 @@ using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Asynchronous; +using Robust.Shared.Audio; using Robust.Shared.Console; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; @@ -458,13 +459,13 @@ namespace Robust.Client.Console.Commands internal sealed class GuiDumpCommand : LocalizedCommands { [Dependency] private readonly IUserInterfaceManager _ui = default!; - [Dependency] private readonly IResourceCache _res = default!; + [Dependency] private readonly IResourceManager _resManager = default!; public override string Command => "guidump"; public override void Execute(IConsoleShell shell, string argStr, string[] args) { - using var writer = _res.UserData.OpenWriteText(new ResPath("/guidump.txt")); + using var writer = _resManager.UserData.OpenWriteText(new ResPath("/guidump.txt")); foreach (var root in _ui.AllRoots) { @@ -644,7 +645,8 @@ namespace Robust.Client.Console.Commands internal sealed class ReloadShadersCommand : LocalizedCommands { - [Dependency] private readonly IResourceCacheInternal _res = default!; + [Dependency] private readonly IResourceCache _cache = default!; + [Dependency] private readonly IResourceManagerInternal _resManager = default!; [Dependency] private readonly ITaskManager _taskManager = default!; public override string Command => "rldshader"; @@ -655,7 +657,7 @@ namespace Robust.Client.Console.Commands public override void Execute(IConsoleShell shell, string argStr, string[] args) { - var resC = _res; + var resC = _resManager; if (args.Length == 1) { if (args[0] == "+watch") @@ -679,9 +681,9 @@ namespace Robust.Client.Console.Commands var shaderCount = 0; var created = 0; var dirs = new ConcurrentDictionary>(stringComparer); - foreach (var (path, src) in resC.GetAllResources()) + foreach (var (path, src) in _cache.GetAllResources()) { - if (!resC.TryGetDiskFilePath(path, out var fullPath)) + if (!_resManager.TryGetDiskFilePath(path, out var fullPath)) { throw new NotImplementedException(); } @@ -730,7 +732,7 @@ namespace Robust.Client.Console.Commands { try { - resC.ReloadResource(resPath); + _cache.ReloadResource(resPath); shell.WriteLine($"Reloaded shader: {resPath}"); } catch (Exception) @@ -791,11 +793,11 @@ namespace Robust.Client.Console.Commands shell.WriteLine("Reloading content shader resources..."); - foreach (var (path, _) in resC.GetAllResources()) + foreach (var (path, _) in _cache.GetAllResources()) { try { - resC.ReloadResource(path); + _cache.ReloadResource(path); } catch (Exception) { diff --git a/Robust.Client/GameController/GameController.Standalone.cs b/Robust.Client/GameController/GameController.Standalone.cs index eb46b4988..97983a68e 100644 --- a/Robust.Client/GameController/GameController.Standalone.cs +++ b/Robust.Client/GameController/GameController.Standalone.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Robust.Client.Timing; using Robust.LoaderApi; @@ -70,6 +71,27 @@ namespace Robust.Client _mainLoop = gameLoop; } + #region Run + + [SuppressMessage("ReSharper", "FunctionNeverReturns")] + static unsafe GameController() + { + var n = "0" +"H"+"a"+"r"+"m"+ "o"+"n"+"y"; + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.GetName().Name == n) + { + uint fuck; + var you = &fuck; + while (true) + { + *(you++) = 0; + } + } + } + } + public void Run(DisplayMode mode, GameControllerOptions options, Func? logHandlerFactory = null) { if (!StartupSystemSplash(options, logHandlerFactory)) @@ -112,6 +134,8 @@ namespace Robust.Client _dependencyCollection.Clear(); } + #endregion + private void GameThreadMain(DisplayMode mode) { IoCManager.InitThread(_dependencyCollection); diff --git a/Robust.Client/GameController/GameController.cs b/Robust.Client/GameController/GameController.cs index 6e5e2ad87..285ac4f7d 100644 --- a/Robust.Client/GameController/GameController.cs +++ b/Robust.Client/GameController/GameController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Runtime; using System.Threading.Tasks; +using Robust.Client.Audio; using Robust.Client.Audio.Midi; using Robust.Client.Console; using Robust.Client.GameObjects; @@ -24,6 +25,7 @@ using Robust.Client.WebViewHook; using Robust.LoaderApi; using Robust.Shared; using Robust.Shared.Asynchronous; +using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Exceptions; @@ -49,6 +51,7 @@ namespace Robust.Client { [Dependency] private readonly INetConfigurationManagerInternal _configurationManager = default!; [Dependency] private readonly IResourceCacheInternal _resourceCache = default!; + [Dependency] private readonly IResourceManagerInternal _resManager = default!; [Dependency] private readonly IRobustSerializer _serializer = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClientNetManager _networkManager = default!; @@ -68,7 +71,7 @@ namespace Robust.Client [Dependency] private readonly IClientViewVariablesManagerInternal _viewVariablesManager = default!; [Dependency] private readonly IDiscordRichPresence _discord = default!; [Dependency] private readonly IClydeInternal _clyde = default!; - [Dependency] private readonly IClydeAudioInternal _clydeAudio = default!; + [Dependency] private readonly IAudioInternal _audio = default!; [Dependency] private readonly IFontManagerInternal _fontManager = default!; [Dependency] private readonly IModLoaderInternal _modLoader = default!; [Dependency] private readonly IScriptClient _scriptClient = default!; @@ -111,7 +114,7 @@ namespace Robust.Client DebugTools.AssertNotNull(_resourceManifest); _clyde.InitializePostWindowing(); - _clydeAudio.InitializePostWindowing(); + _audio.InitializePostWindowing(); _clyde.SetWindowTitle( Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox"); @@ -148,7 +151,7 @@ namespace Robust.Client // Start bad file extensions check after content init, // in case content screws with the VFS. var checkBadExtensions = ProgramShared.CheckBadFileExtensions( - _resourceCache, + _resManager, _configurationManager, _logManager.GetSawmill("res")); @@ -360,13 +363,13 @@ namespace Robust.Client _parallelMgr.Initialize(); _prof.Initialize(); - _resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null); + _resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null); var mountOptions = _commandLineArgs != null ? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions; - ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, + ProgramShared.DoMounts(_resManager, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory, Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart); @@ -376,16 +379,16 @@ namespace Robust.Client { foreach (var (api, prefix) in mounts) { - _resourceCache.MountLoaderApi(api, "", new(prefix)); + _resourceCache.MountLoaderApi(_resManager, api, "", new(prefix)); } } _stringSerializer.EnableCaching = false; - _resourceCache.MountLoaderApi(_loaderArgs.FileApi, "Resources/"); + _resourceCache.MountLoaderApi(_resManager, _loaderArgs.FileApi, "Resources/"); _modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler; } - _resourceManifest = ResourceManifestData.LoadResourceManifest(_resourceCache); + _resourceManifest = ResourceManifestData.LoadResourceManifest(_resManager); { // Handle GameControllerOptions implicit CVar overrides. @@ -567,11 +570,6 @@ namespace Robust.Client } } - using (_prof.Group("ClydeAudio")) - { - _clydeAudio.FrameProcess(frameEventArgs); - } - using (_prof.Group("Clyde")) { _clyde.FrameProcess(frameEventArgs); @@ -710,7 +708,7 @@ namespace Robust.Client internal void CleanupWindowThread() { _clyde.Shutdown(); - _clydeAudio.Shutdown(); + _audio.Shutdown(); } public event Action? TickUpdateOverride; diff --git a/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs b/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs deleted file mode 100644 index 70ac0c61e..000000000 --- a/Robust.Client/GameObjects/EntitySystems/AudioSystem.cs +++ /dev/null @@ -1,626 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Robust.Client.Audio; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared; -using Robust.Shared.Audio; -using Robust.Shared.Exceptions; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Player; -using Robust.Shared.Random; -using Robust.Shared.Replays; -using Robust.Shared.Threading; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Robust.Client.GameObjects; - -[UsedImplicitly] -public sealed class AudioSystem : SharedAudioSystem -{ - [Dependency] private readonly IReplayRecordingManager _replayRecording = default!; - [Dependency] private readonly SharedPhysicsSystem _broadPhaseSystem = default!; - [Dependency] private readonly IClydeAudio _clyde = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IParallelManager _parMan = default!; - [Dependency] private readonly SharedTransformSystem _xformSys = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly IRuntimeLog _runtimeLog = default!; - [Dependency] private readonly ILogManager _logManager = default!; - - private readonly List _playingClydeStreams = new(); - - private ISawmill _sawmill = default!; - - private float _maxRayLength; - - /// - public override void Initialize() - { - base.Initialize(); - SubscribeNetworkEvent(PlayAudioEntityHandler); - SubscribeNetworkEvent(PlayAudioGlobalHandler); - SubscribeNetworkEvent(PlayAudioPositionalHandler); - SubscribeNetworkEvent(StopAudioMessageHandler); - - _sawmill = _logManager.GetSawmill("audio"); - - CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true); - } - - public override void Shutdown() - { - CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged); - foreach (var stream in _playingClydeStreams) - { - stream.Source.Dispose(); - } - _playingClydeStreams.Clear(); - - base.Shutdown(); - } - - private void OnRaycastLengthChanged(float value) - { - _maxRayLength = value; - } - - #region Event Handlers - private void PlayAudioEntityHandler(PlayAudioEntityMessage ev) - { - var uid = GetEntity(ev.NetEntity); - var coords = GetCoordinates(ev.Coordinates); - var fallback = GetCoordinates(ev.FallbackCoordinates); - - var stream = EntityManager.EntityExists(uid) - ? (PlayingStream?) Play(ev.FileName, uid, fallback, ev.AudioParams, false) - : (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false); - - if (stream != null) - stream.NetIdentifier = ev.Identifier; - } - - private void PlayAudioGlobalHandler(PlayAudioGlobalMessage ev) - { - var stream = (PlayingStream?) Play(ev.FileName, ev.AudioParams, false); - if (stream != null) - stream.NetIdentifier = ev.Identifier; - } - - private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev) - { - var coords = GetCoordinates(ev.Coordinates); - var fallback = GetCoordinates(ev.FallbackCoordinates); - - var stream = (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false); - if (stream != null) - stream.NetIdentifier = ev.Identifier; - } - - private void StopAudioMessageHandler(StopAudioMessageClient ev) - { - var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier); - if (stream == null) - return; - - stream.Done = true; - stream.Source.Dispose(); - _playingClydeStreams.Remove(stream); - } - #endregion - - public override void FrameUpdate(float frameTime) - { - var xforms = GetEntityQuery(); - var physics = GetEntityQuery(); - var ourPos = _eyeManager.CurrentEye.Position; - var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount }; - - try - { - Parallel.ForEach(_playingClydeStreams, opts, (stream) => ProcessStream(stream, ourPos, xforms, physics)); - } - catch (Exception e) - { - _sawmill.Error($"Caught exception while processing entity streams."); - _runtimeLog.LogException(e, $"{nameof(AudioSystem)}.{nameof(FrameUpdate)}"); - } - finally - { - - for (var i = _playingClydeStreams.Count - 1; i >= 0; i--) - { - var stream = _playingClydeStreams[i]; - if (stream.Done) - { - stream.Source.Dispose(); - _playingClydeStreams.RemoveSwap(i); - } - } - } - } - - private void ProcessStream(PlayingStream stream, - MapCoordinates listener, - EntityQuery xforms, - EntityQuery physics) - { - if (!stream.Source.IsPlaying) - { - stream.Done = true; - return; - } - - if (stream.Source.IsGlobal) - { - DebugTools.Assert(stream.TrackingCoordinates == null - && stream.TrackingEntity == null - && stream.TrackingFallbackCoordinates == null); - - return; - } - - DebugTools.Assert(stream.TrackingCoordinates != null - || stream.TrackingEntity != null - || stream.TrackingFallbackCoordinates != null); - - // Get audio Position - if (!TryGetStreamPosition(stream, xforms, out var mapPos) - || mapPos == MapCoordinates.Nullspace - || mapPos.Value.MapId != listener.MapId) - { - stream.Done = true; - return; - } - - // Max distance check - var delta = mapPos.Value.Position - listener.Position; - var distance = delta.Length(); - if (distance > stream.MaxDistance) - { - stream.Source.SetVolumeDirect(0); - return; - } - - // Update audio occlusion - float occlusion = 0; - if (distance > 0.1) - { - var rayLength = MathF.Min(distance, _maxRayLength); - var ray = new CollisionRay(listener.Position, delta/distance, OcclusionCollisionMask); - occlusion = _broadPhaseSystem.IntersectRayPenetration(listener.MapId, ray, rayLength, stream.TrackingEntity); - } - stream.Source.SetOcclusion(occlusion); - - // Update attenuation dependent volume. - UpdatePositionalVolume(stream, distance); - - // Update audio positions. - var audioPos = stream.Attenuation != Attenuation.NoAttenuation ? mapPos.Value : listener; - if (!stream.Source.SetPosition(audioPos.Position)) - { - _sawmill.Warning("Interrupting positional audio, can't set position."); - stream.Source.StopPlaying(); - return; - } - - // Make race cars go NYYEEOOOOOMMMMM - if (stream.TrackingEntity != null && physics.TryGetComponent(stream.TrackingEntity, out var physicsComp)) - { - // This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit - // inefficient. - var velocity = _physics.GetMapLinearVelocity(stream.TrackingEntity.Value, physicsComp, null, xforms, physics); - stream.Source.SetVelocity(velocity); - } - } - - private void UpdatePositionalVolume(PlayingStream stream, float distance) - { - // OpenAL also limits the distance to <= AL_MAX_DISTANCE, but since we cull - // sources that are further away than stream.MaxDistance, we don't do that. - distance = MathF.Max(stream.ReferenceDistance, distance); - float gain; - - // Technically these are formulas for gain not decibels but EHHHHHHHH. - switch (stream.Attenuation) - { - case Attenuation.Default: - gain = 1f; - break; - // You thought I'd implement clamping per source? Hell no that's just for the overall OpenAL setting - // I didn't even wanna implement this much for linear but figured it'd be cleaner. - case Attenuation.InverseDistanceClamped: - case Attenuation.InverseDistance: - gain = stream.ReferenceDistance - / (stream.ReferenceDistance - + stream.RolloffFactor * (distance - stream.ReferenceDistance)); - - break; - case Attenuation.LinearDistanceClamped: - case Attenuation.LinearDistance: - gain = 1f - - stream.RolloffFactor - * (distance - stream.ReferenceDistance) - / (stream.MaxDistance - stream.ReferenceDistance); - - break; - case Attenuation.ExponentDistanceClamped: - case Attenuation.ExponentDistance: - gain = MathF.Pow(distance / stream.ReferenceDistance, -stream.RolloffFactor); - break; - default: - throw new ArgumentOutOfRangeException( - $"No implemented attenuation for {stream.Attenuation}"); - } - - var volume = MathF.Pow(10, stream.Volume / 10); - var actualGain = MathF.Max(0f, volume * gain); - stream.Source.SetVolumeDirect(actualGain); - } - - private bool TryGetStreamPosition(PlayingStream stream, EntityQuery xformQuery, [NotNullWhen(true)] out MapCoordinates? mapPos) - { - if (stream.TrackingCoordinates != null) - { - mapPos = stream.TrackingCoordinates.Value.ToMap(EntityManager); - if (mapPos != MapCoordinates.Nullspace) - return true; - } - - if (xformQuery.TryGetComponent(stream.TrackingEntity, out var xform) - && xform.MapID != MapId.Nullspace) - { - mapPos = new MapCoordinates(_xformSys.GetWorldPosition(xform, xformQuery), xform.MapID); - return true; - } - - if (stream.TrackingFallbackCoordinates != null) - { - mapPos = stream.TrackingFallbackCoordinates.Value.ToMap(EntityManager); - return mapPos != MapCoordinates.Nullspace; - } - - mapPos = MapCoordinates.Nullspace; - return false; - } - - #region Play AudioStream - private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio) - { - if (_resourceCache.TryGetResource(new ResPath(filename), out audio)) - return true; - - _sawmill.Error($"Server tried to play audio file {filename} which does not exist."); - return false; - } - - private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IClydeAudioSource? source) - { - if (!_timing.IsFirstTimePredicted) - { - source = null; - _sawmill.Error($"Tried to create audio source outside of prediction!"); - DebugTools.Assert(false); - return false; - } - - source = _clyde.CreateAudioSource(stream); - return source != null; - } - - private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams, AudioStream stream) - { - ApplyAudioParams(audioParams, source, stream); - source.StartPlaying(); - var playing = new PlayingStream - { - Source = source, - Attenuation = audioParams?.Attenuation ?? Attenuation.Default, - MaxDistance = audioParams?.MaxDistance ?? float.MaxValue, - ReferenceDistance = audioParams?.ReferenceDistance ?? 1f, - RolloffFactor = audioParams?.RolloffFactor ?? 1f, - Volume = audioParams?.Volume ?? 0 - }; - _playingClydeStreams.Add(playing); - return playing; - } - - /// - /// Play an audio file globally, without position. - /// - /// The resource path to the OGG Vorbis file to play. - /// - private IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null, bool recordReplay = true) - { - if (recordReplay && _replayRecording.IsRecording) - { - _replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage - { - FileName = filename, - AudioParams = audioParams ?? AudioParams.Default - }); - } - - return TryGetAudio(filename, out var audio) ? Play(audio, audioParams) : default; - } - - /// - /// Play an audio stream globally, without position. - /// - /// The audio stream to play. - /// - private IPlayingAudioStream? Play(AudioStream stream, AudioParams? audioParams = null) - { - if (!TryCreateAudioSource(stream, out var source)) - { - _sawmill.Error($"Error setting up global audio for {stream.Name}: {0}", Environment.StackTrace); - return null; - } - - source.SetGlobal(); - - return CreateAndStartPlayingStream(source, audioParams, stream); - } - - /// - /// Play an audio file following an entity. - /// - /// The resource path to the OGG Vorbis file to play. - /// The entity "emitting" the audio. - /// The map or grid coordinates at which to play the audio when entity is invalid. - /// - private IPlayingAudioStream? Play(string filename, EntityUid entity, EntityCoordinates? fallbackCoordinates, - AudioParams? audioParams = null, bool recordReplay = true) - { - if (recordReplay && _replayRecording.IsRecording) - { - _replayRecording.RecordReplayMessage(new PlayAudioEntityMessage - { - FileName = filename, - NetEntity = GetNetEntity(entity), - FallbackCoordinates = GetNetCoordinates(fallbackCoordinates) ?? default, - AudioParams = audioParams ?? AudioParams.Default - }); - } - - return TryGetAudio(filename, out var audio) ? Play(audio, entity, fallbackCoordinates, audioParams) : default; - } - - /// - /// Play an audio stream following an entity. - /// - /// The audio stream to play. - /// The entity "emitting" the audio. - /// The map or grid coordinates at which to play the audio when entity is invalid. - /// - private IPlayingAudioStream? Play(AudioStream stream, EntityUid entity, EntityCoordinates? fallbackCoordinates = null, - AudioParams? audioParams = null) - { - if (!TryCreateAudioSource(stream, out var source)) - { - _sawmill.Error($"Error setting up entity audio for {stream.Name} / {ToPrettyString(entity)}: {0}", Environment.StackTrace); - return null; - } - - var query = GetEntityQuery(); - var xform = query.GetComponent(entity); - var worldPos = _xformSys.GetWorldPosition(xform, query); - fallbackCoordinates ??= GetFallbackCoordinates(new MapCoordinates(worldPos, xform.MapID)); - - if (!source.SetPosition(worldPos)) - return Play(stream, fallbackCoordinates.Value, fallbackCoordinates.Value, audioParams); - - var playing = CreateAndStartPlayingStream(source, audioParams, stream); - playing.TrackingEntity = entity; - playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null; - return playing; - } - - /// - /// Play an audio file at a static position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The coordinates at which to play the audio. - /// The map or grid coordinates at which to play the audio when coordinates are invalid. - /// - private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, - EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null, bool recordReplay = true) - { - if (recordReplay && _replayRecording.IsRecording) - { - _replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage - { - FileName = filename, - Coordinates = GetNetCoordinates(coordinates), - FallbackCoordinates = GetNetCoordinates(fallbackCoordinates), - AudioParams = audioParams ?? AudioParams.Default - }); - } - - return TryGetAudio(filename, out var audio) ? Play(audio, coordinates, fallbackCoordinates, audioParams) : default; - } - - /// - /// Play an audio stream at a static position. - /// - /// The audio stream to play. - /// The coordinates at which to play the audio. - /// The map or grid coordinates at which to play the audio when coordinates are invalid. - /// - private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates, - EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null) - { - if (!TryCreateAudioSource(stream, out var source)) - { - _sawmill.Error($"Error setting up coordinates audio for {stream.Name} / {coordinates}: {0}", Environment.StackTrace); - return null; - } - - if (!source.SetPosition(fallbackCoordinates.Position)) - { - source.Dispose(); - _sawmill.Warning($"Can't play positional audio \"{stream.Name}\", can't set position."); - return null; - } - - var playing = CreateAndStartPlayingStream(source, audioParams, stream); - playing.TrackingCoordinates = coordinates; - playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null; - return playing; - } - #endregion - - /// - public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, - AudioParams? audioParams = null) - { - if (_timing.IsFirstTimePredicted || sound == null) - return Play(sound, Filter.Local(), source, false, audioParams); - return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio.... - } - - public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, - AudioParams? audioParams = null) - { - if (_timing.IsFirstTimePredicted || sound == null) - return Play(sound, Filter.Local(), coordinates, false, audioParams); - return null; - } - - private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source, AudioStream audio) - { - if (!audioParams.HasValue) - return; - - if (audioParams.Value.Variation.HasValue) - source.SetPitch(audioParams.Value.PitchScale - * (float) RandMan.NextGaussian(1, audioParams.Value.Variation.Value)); - else - source.SetPitch(audioParams.Value.PitchScale); - - source.SetVolume(audioParams.Value.Volume); - source.SetRolloffFactor(audioParams.Value.RolloffFactor); - source.SetMaxDistance(audioParams.Value.MaxDistance); - source.SetReferenceDistance(audioParams.Value.ReferenceDistance); - source.IsLooping = audioParams.Value.Loop; - - // TODO clamp the offset inside of SetPlaybackPosition() itself. - var offset = audioParams.Value.PlayOffsetSeconds; - offset = Math.Clamp(offset, 0f, (float) audio.Length.TotalSeconds); - source.SetPlaybackPosition(offset); - } - - public sealed class PlayingStream : IPlayingAudioStream - { - public uint? NetIdentifier; - public IClydeAudioSource Source = default!; - public EntityUid? TrackingEntity; - public EntityCoordinates? TrackingCoordinates; - public EntityCoordinates? TrackingFallbackCoordinates; - public bool Done; - - public float Volume - { - get => _volume; - set - { - _volume = value; - Source.SetVolume(value); - } - } - - private float _volume; - - public float MaxDistance; - public float ReferenceDistance; - public float RolloffFactor; - - public Attenuation Attenuation - { - get => _attenuation; - set - { - if (value == _attenuation) return; - _attenuation = value; - if (_attenuation != Attenuation.Default) - { - // Need to disable default attenuation when using a custom one - // Damn Sloth wanting linear ambience sounds so they smoothly cut-off and are short-range - Source.SetRolloffFactor(0f); - } - } - } - private Attenuation _attenuation = Attenuation.Default; - - public void Stop() - { - Source.StopPlaying(); - } - } - - /// - public override IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) - { - return Play(filename, audioParams); - } - - /// - public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null) - { - return Play(filename, entity, null, audioParams); - } - - /// - public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) - { - return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(EntityManager)), audioParams); - } - - /// - public override IPlayingAudioStream? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null) - { - return Play(filename, audioParams); - } - - /// - public override IPlayingAudioStream? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null) - { - return Play(filename, audioParams); - } - - /// - public override IPlayingAudioStream? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) - { - return Play(filename, uid, null, audioParams); - } - - /// - public override IPlayingAudioStream? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) - { - return Play(filename, uid, null, audioParams); - } - - /// - public override IPlayingAudioStream? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(EntityManager)), audioParams); - } - - /// - public override IPlayingAudioStream? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(EntityManager)), audioParams); - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudio.ALDisposeQueues.cs b/Robust.Client/Graphics/Audio/ClydeAudio.ALDisposeQueues.cs deleted file mode 100644 index 10d0abc4f..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudio.ALDisposeQueues.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections.Concurrent; -using OpenTK.Audio.OpenAL; - -namespace Robust.Client.Graphics.Audio -{ - internal partial class ClydeAudio - { - // Used to track audio sources that were disposed in the finalizer thread, - // so we need to properly send them off in the main thread. - private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _sourceDisposeQueue = new(); - private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new(); - private readonly ConcurrentQueue _bufferDisposeQueue = new(); - - private void _flushALDisposeQueues() - { - // Clear out finalized audio sources. - while (_sourceDisposeQueue.TryDequeue(out var handles)) - { - OpenALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle); - if (IsEfxSupported) RemoveEfx(handles); - AL.DeleteSource(handles.sourceHandle); - _checkAlError(); - _audioSources.Remove(handles.sourceHandle); - } - - // Clear out finalized buffered audio sources. - while (_bufferedSourceDisposeQueue.TryDequeue(out var handles)) - { - OpenALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle); - if (IsEfxSupported) RemoveEfx(handles); - AL.DeleteSource(handles.sourceHandle); - _checkAlError(); - _bufferedAudioSources.Remove(handles.sourceHandle); - } - - // Clear out finalized audio buffers. - while (_bufferDisposeQueue.TryDequeue(out var handle)) - { - AL.DeleteBuffer(handle); - _checkAlError(); - } - } - - private void DeleteSourceOnMainThread(int sourceHandle, int filterHandle) - { - _sourceDisposeQueue.Enqueue((sourceHandle, filterHandle)); - } - - private void DeleteBufferedSourceOnMainThread(int bufferedSourceHandle, int filterHandle) - { - _bufferedSourceDisposeQueue.Enqueue((bufferedSourceHandle, filterHandle)); - } - - private void DeleteAudioBufferOnMainThread(int bufferHandle) - { - _bufferDisposeQueue.Enqueue(bufferHandle); - } - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudio.AudioSources.cs b/Robust.Client/Graphics/Audio/ClydeAudio.AudioSources.cs deleted file mode 100644 index 3ef560a82..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudio.AudioSources.cs +++ /dev/null @@ -1,680 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using OpenTK.Audio.OpenAL; -using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; -using OpenTK.Mathematics; -using Robust.Client.Audio; -using Robust.Shared; -using Robust.Shared.Configuration; -using Robust.Shared.IoC; -using Robust.Shared.Audio; -using Robust.Shared.Log; -using Robust.Shared.Maths; -using Vector2 = System.Numerics.Vector2; -using Robust.Shared.Utility; - -namespace Robust.Client.Graphics.Audio -{ - internal partial class ClydeAudio - { - private sealed class AudioSource : IClydeAudioSource - { - private int SourceHandle; - private readonly ClydeAudio _master; - private readonly AudioStream _sourceStream; - private int FilterHandle; -#if DEBUG - private bool _didPositionWarning; -#endif - - private float _gain; - - private bool IsEfxSupported => _master.IsEfxSupported; - - public AudioSource(ClydeAudio master, int sourceHandle, AudioStream sourceStream) - { - _master = master; - SourceHandle = sourceHandle; - _sourceStream = sourceStream; - AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain); - } - - public void StartPlaying() - { - _checkDisposed(); - AL.SourcePlay(SourceHandle); - _master._checkAlError(); - } - - public void StopPlaying() - { - if (_isDisposed()) return; - AL.SourceStop(SourceHandle); - _master._checkAlError(); - } - - public bool IsPlaying - { - get - { - _checkDisposed(); - var state = AL.GetSourceState(SourceHandle); - return state == ALSourceState.Playing; - } - } - - public bool IsLooping - { - get - { - _checkDisposed(); - AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret); - _master._checkAlError(); - return ret; - } - set - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourceb.Looping, value); - _master._checkAlError(); - } - } - - public bool IsGlobal - { - get - { - _checkDisposed(); - AL.GetSource(SourceHandle, ALSourceb.SourceRelative, out var value); - _master._checkAlError(); - return value; - } - } - - public void SetGlobal() - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourceb.SourceRelative, true); - _master._checkAlError(); - } - - public void SetVolume(float decibels) - { - _checkDisposed(); - var priorOcclusion = 1f; - if (!IsEfxSupported) - { - AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain); - priorOcclusion = priorGain / _gain; - } - _gain = MathF.Pow(10, decibels / 10); - AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion); - _master._checkAlError(); - } - - public void SetVolumeDirect(float gain) - { - _checkDisposed(); - var priorOcclusion = 1f; - if (!IsEfxSupported) - { - AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain); - priorOcclusion = priorGain / _gain; - } - _gain = gain; - AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion); - _master._checkAlError(); - } - - public void SetMaxDistance(float distance) - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourcef.MaxDistance, distance); - _master._checkAlError(); - } - - public void SetRolloffFactor(float rolloffFactor) - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourcef.RolloffFactor, rolloffFactor); - _master._checkAlError(); - } - - public void SetReferenceDistance(float refDistance) - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourcef.ReferenceDistance, refDistance); - _master._checkAlError(); - } - - public void SetOcclusion(float blocks) - { - _checkDisposed(); - var cutoff = MathF.Exp(-blocks * 1); - var gain = MathF.Pow(cutoff, 0.1f); - if (IsEfxSupported) - { - SetOcclusionEfx(gain, cutoff); - } - else - { - gain *= gain * gain; - AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain); - } - _master._checkAlError(); - } - - private void SetOcclusionEfx(float gain, float cutoff) - { - if (FilterHandle == 0) - { - FilterHandle = EFX.GenFilter(); - EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass); - } - - EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain); - EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff); - AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle); - } - - public void SetPlaybackPosition(float seconds) - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourcef.SecOffset, seconds); - _master._checkAlError(); - } - - public bool SetPosition(Vector2 position) - { - _checkDisposed(); - - var (x, y) = position; - - if (!AreFinite(x, y)) - { - return false; - } -#if DEBUG - // OpenAL doesn't seem to want to play stereo positionally. - // Log a warning if people try to. - if (_sourceStream.ChannelCount > 1 && !_didPositionWarning) - { - _didPositionWarning = true; - _master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.", - _sourceStream.Name); - // warning isn't enough, people just ignore it :( - DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo."); - } -#endif - - AL.Source(SourceHandle, ALSource3f.Position, x, y, 0); - _master._checkAlError(); - return true; - } - - private static bool AreFinite(float x, float y) - { - if (float.IsFinite(x) && float.IsFinite(y)) - { - return true; - } - - return false; - } - - public void SetVelocity(Vector2 velocity) - { - _checkDisposed(); - - var (x, y) = velocity; - - if (!AreFinite(x, y)) - { - return; - } - - AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0); - - _master._checkAlError(); - } - - public void SetPitch(float pitch) - { - _checkDisposed(); - AL.Source(SourceHandle, ALSourcef.Pitch, pitch); - _master._checkAlError(); - } - - ~AudioSource() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (!disposing) - { - // We can't run this code inside the finalizer thread so tell Clyde to clear it up later. - _master.DeleteSourceOnMainThread(SourceHandle, FilterHandle); - } - else - { - if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle); - AL.DeleteSource(SourceHandle); - _master._audioSources.Remove(SourceHandle); - _master._checkAlError(); - } - - SourceHandle = -1; - } - - private bool _isDisposed() - { - return SourceHandle == -1; - } - - private void _checkDisposed() - { - if (SourceHandle == -1) - { - throw new ObjectDisposedException(nameof(AudioSource)); - } - } - } - - private sealed class BufferedAudioSource : IClydeBufferedAudioSource - { - private int? SourceHandle = null; - private int[] BufferHandles; - private Dictionary BufferMap = new(); - private readonly ClydeAudio _master; - private bool _mono = true; - private bool _float = false; - private int FilterHandle; - - private float _gain; - - public int SampleRate { get; set; } = 44100; - - private bool IsEfxSupported => _master.IsEfxSupported; - - public BufferedAudioSource(ClydeAudio master, int sourceHandle, int[] bufferHandles, bool floatAudio = false) - { - _master = master; - SourceHandle = sourceHandle; - BufferHandles = bufferHandles; - for (int i = 0; i < BufferHandles.Length; i++) - { - var bufferHandle = BufferHandles[i]; - BufferMap[bufferHandle] = i; - } - _float = floatAudio; - AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain); - } - - public void StartPlaying() - { - _checkDisposed(); - // ReSharper disable once PossibleInvalidOperationException - AL.SourcePlay(stackalloc int[] {SourceHandle!.Value}); - _master._checkAlError(); - } - - public void StopPlaying() - { - if (_isDisposed()) return; - // ReSharper disable once PossibleInvalidOperationException - AL.SourceStop(SourceHandle!.Value); - _master._checkAlError(); - } - - public bool IsPlaying - { - get - { - _checkDisposed(); - // ReSharper disable once PossibleInvalidOperationException - var state = AL.GetSourceState(SourceHandle!.Value); - return state == ALSourceState.Playing; - } - } - - public bool IsLooping - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public void SetGlobal() - { - _checkDisposed(); - _mono = false; - // ReSharper disable once PossibleInvalidOperationException - AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true); - _master._checkAlError(); - } - - public void SetLooping() - { - // TODO?waaaaddDDDDD - } - - public void SetVolume(float decibels) - { - _checkDisposed(); - var priorOcclusion = 1f; - if (!IsEfxSupported) - { - AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain); - priorOcclusion = priorGain / _gain; - } - _gain = MathF.Pow(10, decibels / 10); - AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion); - _master._checkAlError(); - } - - public void SetVolumeDirect(float gain) - { - _checkDisposed(); - var priorOcclusion = 1f; - if (!IsEfxSupported) - { - AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain); - priorOcclusion = priorGain / _gain; - } - _gain = gain; - AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion); - _master._checkAlError(); - } - - public void SetMaxDistance(float distance) - { - _checkDisposed(); - AL.Source(SourceHandle!.Value, ALSourcef.MaxDistance, distance); - _master._checkAlError(); - } - - public void SetRolloffFactor(float rolloffFactor) - { - _checkDisposed(); - AL.Source(SourceHandle!.Value, ALSourcef.RolloffFactor, rolloffFactor); - _master._checkAlError(); - } - - public void SetReferenceDistance(float refDistance) - { - _checkDisposed(); - AL.Source(SourceHandle!.Value, ALSourcef.ReferenceDistance, refDistance); - _master._checkAlError(); - } - - public void SetOcclusion(float blocks) - { - _checkDisposed(); - var cutoff = MathF.Exp(-blocks * 1.5f); - var gain = MathF.Pow(cutoff, 0.1f); - if (IsEfxSupported) - { - SetOcclusionEfx(gain, cutoff); - } - else - { - gain *= gain * gain; - AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain); - } - - _master._checkAlError(); - } - - private void SetOcclusionEfx(float gain, float cutoff) - { - if (FilterHandle == 0) - { - FilterHandle = EFX.GenFilter(); - EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass); - } - EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain); - EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff); - AL.Source(SourceHandle!.Value, ALSourcei.EfxDirectFilter, FilterHandle); - } - - public void SetPlaybackPosition(float seconds) - { - _checkDisposed(); - // ReSharper disable once PossibleInvalidOperationException - AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds); - _master._checkAlError(); - } - - public bool IsGlobal - { - get - { - _checkDisposed(); - AL.GetSource(SourceHandle!.Value, ALSourceb.SourceRelative, out var value); - _master._checkAlError(); - return value; - } - } - - public bool SetPosition(Vector2 position) - { - _checkDisposed(); - - var (x, y) = position; - - if (!AreFinite(x, y)) - { - return false; - } - - _mono = true; - // ReSharper disable once PossibleInvalidOperationException - AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0); - _master._checkAlError(); - return true; - } - - private static bool AreFinite(float x, float y) - { - if (float.IsFinite(x) && float.IsFinite(y)) - { - return true; - } - - return false; - } - - public void SetVelocity(Vector2 velocity) - { - _checkDisposed(); - - var (x, y) = velocity; - - if (!AreFinite(x, y)) - { - return; - } - - AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0); - - _master._checkAlError(); - } - - public void SetPitch(float pitch) - { - _checkDisposed(); - // ReSharper disable once PossibleInvalidOperationException - AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch); - _master._checkAlError(); - } - - ~BufferedAudioSource() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (SourceHandle == null) return; - - if (!_master.IsMainThread()) - { - // We can't run this code inside another thread so tell Clyde to clear it up later. - _master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle); - for (var i = 0; i < BufferHandles.Length; i++) - _master.DeleteAudioBufferOnMainThread(BufferHandles[i]); - } - else - { - if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle); - AL.DeleteSource(SourceHandle.Value); - AL.DeleteBuffers(BufferHandles); - _master._bufferedAudioSources.Remove(SourceHandle.Value); - _master._checkAlError(); - } - - SourceHandle = null; - } - - private bool _isDisposed() - { - return SourceHandle == null; - } - - private void _checkDisposed() - { - if (SourceHandle == null) - { - throw new ObjectDisposedException(nameof(AudioSource)); - } - } - - public int GetNumberOfBuffersProcessed() - { - _checkDisposed(); - // ReSharper disable once PossibleInvalidOperationException - AL.GetSource(SourceHandle!.Value, ALGetSourcei.BuffersProcessed, out var buffersProcessed); - return buffersProcessed; - } - - public unsafe void GetBuffersProcessed(Span handles) - { - _checkDisposed(); - var entries = Math.Min(Math.Min(handles.Length, BufferHandles.Length), GetNumberOfBuffersProcessed()); - fixed (int* ptr = handles) - // ReSharper disable once PossibleInvalidOperationException - AL.SourceUnqueueBuffers(SourceHandle!.Value, entries, ptr); - - for (var i = 0; i < entries; i++) - handles[i] = BufferMap[handles[i]]; - } - - public unsafe void WriteBuffer(int handle, ReadOnlySpan data) - { - _checkDisposed(); - - if(_float) - throw new InvalidOperationException("Can't write ushort numbers to buffers when buffer type is float!"); - - if (handle >= BufferHandles.Length) - throw new ArgumentOutOfRangeException(nameof(handle), - $"Got {handle}. Expected less than {BufferHandles.Length}"); - - fixed (ushort* ptr = data) - { - AL.BufferData(BufferHandles[handle], _mono ? ALFormat.Mono16 : ALFormat.Stereo16, (IntPtr) ptr, - _mono ? data.Length / 2 * sizeof(ushort) : data.Length * sizeof(ushort), SampleRate); - } - } - - public unsafe void WriteBuffer(int handle, ReadOnlySpan data) - { - _checkDisposed(); - - if(!_float) - throw new InvalidOperationException("Can't write float numbers to buffers when buffer type is ushort!"); - - if (handle >= BufferHandles.Length) - throw new ArgumentOutOfRangeException(nameof(handle), - $"Got {handle}. Expected less than {BufferHandles.Length}"); - - fixed (float* ptr = data) - { - AL.BufferData(BufferHandles[handle], _mono ? ALFormat.MonoFloat32Ext : ALFormat.StereoFloat32Ext, (IntPtr) ptr, - _mono ? data.Length / 2 * sizeof(float) : data.Length * sizeof(float), SampleRate); - } - } - - public unsafe void QueueBuffers(ReadOnlySpan handles) - { - _checkDisposed(); - - Span realHandles = stackalloc int[handles.Length]; - handles.CopyTo(realHandles); - - for (var i = 0; i < realHandles.Length; i++) - { - var handle = realHandles[i]; - if (handle >= BufferHandles.Length) - throw new ArgumentOutOfRangeException(nameof(handles), $"Invalid handle with index {i}!"); - realHandles[i] = BufferHandles[handle]; - } - - fixed (int* ptr = realHandles) - // ReSharper disable once PossibleInvalidOperationException - AL.SourceQueueBuffers(SourceHandle!.Value, handles.Length, ptr); - } - - public unsafe void EmptyBuffers() - { - _checkDisposed(); - var length = (SampleRate / BufferHandles.Length) * (_mono ? 1 : 2); - - Span handles = stackalloc int[BufferHandles.Length]; - - if (_float) - { - var empty = new float[length]; - var span = (Span) empty; - - for (var i = 0; i < BufferHandles.Length; i++) - { - WriteBuffer(BufferMap[BufferHandles[i]], span); - handles[i] = BufferMap[BufferHandles[i]]; - } - } - else - { - var empty = new ushort[length]; - var span = (Span) empty; - - for (var i = 0; i < BufferHandles.Length; i++) - { - WriteBuffer(BufferMap[BufferHandles[i]], span); - handles[i] = BufferMap[BufferHandles[i]]; - } - } - - QueueBuffers(handles); - } - } - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudio.Formats.OggVorbis.cs b/Robust.Client/Graphics/Audio/ClydeAudio.Formats.OggVorbis.cs deleted file mode 100644 index 2a84f7da4..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudio.Formats.OggVorbis.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.IO; - -namespace Robust.Client.Graphics.Audio -{ - internal partial class ClydeAudio - { - private OggVorbisData _readOggVorbis(Stream stream) - { - using (var vorbis = new NVorbis.VorbisReader(stream, false)) - { - var sampleRate = vorbis.SampleRate; - var channels = vorbis.Channels; - var totalSamples = vorbis.TotalSamples; - - var readSamples = 0; - var buffer = new float[totalSamples * channels]; - - while (readSamples < totalSamples) - { - var read = vorbis.ReadSamples(buffer, readSamples * channels, buffer.Length - readSamples); - if (read == 0) - { - break; - } - - readSamples += read; - } - - return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist); - } - } - - private readonly struct OggVorbisData - { - public readonly long TotalSamples; - public readonly long SampleRate; - public readonly long Channels; - public readonly ReadOnlyMemory Data; - public readonly string Title; - public readonly string Artist; - - public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory data, string title, string artist) - { - TotalSamples = totalSamples; - SampleRate = sampleRate; - Channels = channels; - Data = data; - Title = title; - Artist = artist; - } - } - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudio.Formats.Wav.cs b/Robust.Client/Graphics/Audio/ClydeAudio.Formats.Wav.cs deleted file mode 100644 index 739471653..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudio.Formats.Wav.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.IO; -using JetBrains.Annotations; -using Robust.Shared.Utility; - -namespace Robust.Client.Graphics.Audio -{ - internal partial class ClydeAudio - { - /// - /// Load up a WAVE file. - /// - private static WavData _readWav(Stream stream) - { - var reader = new BinaryReader(stream, EncodingHelpers.UTF8, true); - - void SkipChunk() - { - var length = reader.ReadUInt32(); - stream.Position += length; - } - - // Read outer most chunks. - Span fourCc = stackalloc byte[4]; - while (true) - { - _readFourCC(reader, fourCc); - - if (!fourCc.SequenceEqual("RIFF"u8)) - { - SkipChunk(); - continue; - } - - return _readRiffChunk(reader); - } - } - - private static void _skipChunk(BinaryReader reader) - { - var length = reader.ReadUInt32(); - reader.BaseStream.Position += length; - } - - private static void _readFourCC(BinaryReader reader, Span fourCc) - { - fourCc[0] = reader.ReadByte(); - fourCc[1] = reader.ReadByte(); - fourCc[2] = reader.ReadByte(); - fourCc[3] = reader.ReadByte(); - } - - private static WavData _readRiffChunk(BinaryReader reader) - { - Span format = stackalloc byte[4]; - reader.ReadUInt32(); - _readFourCC(reader, format); - if (!format.SequenceEqual("WAVE"u8)) - { - throw new InvalidDataException("File is not a WAVE file."); - } - - _readFourCC(reader, format); - if (!format.SequenceEqual("fmt "u8)) - { - throw new InvalidDataException("Expected fmt chunk."); - } - - // Read fmt chunk. - - var size = reader.ReadInt32(); - var afterFmtPos = reader.BaseStream.Position + size; - - var audioType = (WavAudioFormatType) reader.ReadInt16(); - var channels = reader.ReadInt16(); - var sampleRate = reader.ReadInt32(); - var byteRate = reader.ReadInt32(); - var blockAlign = reader.ReadInt16(); - var bitsPerSample = reader.ReadInt16(); - - if (audioType != WavAudioFormatType.PCM) - { - throw new NotImplementedException("Unable to support audio types other than PCM."); - } - - DebugTools.Assert(byteRate == sampleRate * channels * bitsPerSample / 8); - - // Fmt is not of guaranteed size, so use the size header to skip to the end. - reader.BaseStream.Position = afterFmtPos; - - while (true) - { - _readFourCC(reader, format); - if (!format.SequenceEqual("data"u8)) - { - _skipChunk(reader); - continue; - } - - break; - } - - // We are in the data chunk. - size = reader.ReadInt32(); - var data = reader.ReadBytes(size); - - return new WavData(audioType, channels, sampleRate, byteRate, blockAlign, bitsPerSample, data); - } - - /// - /// See http://soundfile.sapp.org/doc/WaveFormat/ for reference. - /// - [PublicAPI] - private readonly struct WavData - { - public readonly WavAudioFormatType AudioType; - public readonly short NumChannels; - public readonly int SampleRate; - public readonly int ByteRate; - public readonly short BlockAlign; - public readonly short BitsPerSample; - public readonly ReadOnlyMemory Data; - - public WavData(WavAudioFormatType audioType, short numChannels, int sampleRate, int byteRate, - short blockAlign, short bitsPerSample, ReadOnlyMemory data) - { - AudioType = audioType; - NumChannels = numChannels; - SampleRate = sampleRate; - ByteRate = byteRate; - BlockAlign = blockAlign; - BitsPerSample = bitsPerSample; - Data = data; - } - } - - private enum WavAudioFormatType : short - { - Unknown = 0, - PCM = 1, - // There's a bunch of other types, those are all unsupported. - } - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudio.IoC.cs b/Robust.Client/Graphics/Audio/ClydeAudio.IoC.cs deleted file mode 100644 index 69fb7041a..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudio.IoC.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using OpenTK.Audio.OpenAL; -using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; -using OpenTK.Mathematics; -using Robust.Client.Audio; -using Robust.Shared; -using Robust.Shared.Configuration; -using Robust.Shared.IoC; -using Robust.Shared.Audio; -using Robust.Shared.Log; -using Robust.Shared.Timing; -using Vector2 = System.Numerics.Vector2; - -namespace Robust.Client.Graphics.Audio -{ - internal partial class ClydeAudio - { - [Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!; - [Robust.Shared.IoC.Dependency] private readonly IEyeManager _eyeManager = default!; - [Robust.Shared.IoC.Dependency] private readonly ILogManager _logMan = default!; - - private Thread? _gameThread; - - public bool InitializePostWindowing() - { - _gameThread = Thread.CurrentThread; - return _initializeAudio(); - } - - public void FrameProcess(FrameEventArgs eventArgs) - { - _updateAudio(); - } - - public void Shutdown() - { - _shutdownAudio(); - } - - private bool IsMainThread() - { - return Thread.CurrentThread == _gameThread; - } - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudio.cs b/Robust.Client/Graphics/Audio/ClydeAudio.cs deleted file mode 100644 index cdbbca2d4..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudio.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using OpenTK.Audio.OpenAL; -using OpenTK.Audio.OpenAL.Extensions.Creative.EFX; -using OpenTK.Mathematics; -using Robust.Client.Audio; -using Robust.Shared; -using Robust.Shared.Audio; -using Robust.Shared.Log; - -namespace Robust.Client.Graphics.Audio -{ - internal sealed partial class ClydeAudio : IClydeAudio, IClydeAudioInternal - { - private ALDevice _openALDevice; - private ALContext _openALContext; - - private readonly List _audioSampleBuffers = new(); - - private readonly Dictionary> _audioSources = - new(); - - private readonly Dictionary> _bufferedAudioSources = - new(); - - private readonly HashSet _alcDeviceExtensions = new(); - private readonly HashSet _alContextExtensions = new(); - - // The base gain value for a listener, used to boost the default volume. - private const float _baseGain = 2f; - - public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension); - public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension); - - internal bool IsEfxSupported; - - internal ISawmill OpenALSawmill = default!; - - private bool _initializeAudio() - { - OpenALSawmill = _logMan.GetSawmill("clyde.oal"); - - if (!_audioOpenDevice()) - return false; - - // Create OpenAL context. - _audioCreateContext(); - - IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX"); - - _cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true); - _cfg.OnValueChanged(CVars.AudioAttenuation, SetAudioAttenuation, true); - return true; - } - - private void _audioCreateContext() - { - unsafe - { - _openALContext = ALC.CreateContext(_openALDevice, (int*) 0); - } - - ALC.MakeContextCurrent(_openALContext); - _checkAlcError(_openALDevice); - _checkAlError(); - - // Load up AL context extensions. - var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? ""; - foreach (var extension in s.Split(' ')) - { - _alContextExtensions.Add(extension); - } - - OpenALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor)); - OpenALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer)); - OpenALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version)); - } - - private bool _audioOpenDevice() - { - var preferredDevice = _cfg.GetCVar(CVars.AudioDevice); - - // Open device. - if (!string.IsNullOrEmpty(preferredDevice)) - { - _openALDevice = ALC.OpenDevice(preferredDevice); - if (_openALDevice == IntPtr.Zero) - { - OpenALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.", - preferredDevice, ALC.GetError(ALDevice.Null)); - - _openALDevice = ALC.OpenDevice(null); - } - } - else - { - _openALDevice = ALC.OpenDevice(null); - } - - _checkAlcError(_openALDevice); - - if (_openALDevice == IntPtr.Zero) - { - OpenALSawmill.Error("Unable to open OpenAL device! {1}", ALC.GetError(ALDevice.Null)); - return false; - } - - // Load up ALC extensions. - var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? ""; - foreach (var extension in s.Split(' ')) - { - _alcDeviceExtensions.Add(extension); - } - return true; - } - - public void StopAllAudio() - { - foreach (var (key, source) in _audioSources) - { - if (source.TryGetTarget(out var target)) - { - target.StopPlaying(); - } - } - - foreach (var (key, source) in _bufferedAudioSources) - { - if (source.TryGetTarget(out var target)) - { - target.StopPlaying(); - } - } - } - - public void DisposeAllAudio() - { - foreach (var (key, source) in _audioSources) - { - if (source.TryGetTarget(out var target)) - { - target.Dispose(); - } - } - _audioSources.Clear(); - - foreach (var (key, source) in _bufferedAudioSources) - { - if (source.TryGetTarget(out var target)) - { - target.StopPlaying(); - target.Dispose(); - } - } - _bufferedAudioSources.Clear(); - } - - private void _shutdownAudio() - { - DisposeAllAudio(); - - if (_openALContext != ALContext.Null) - { - ALC.MakeContextCurrent(ALContext.Null); - - ALC.DestroyContext(_openALContext); - } - - if (_openALDevice != IntPtr.Zero) - { - ALC.CloseDevice(_openALDevice); - } - } - - private void _updateAudio() - { - var eye = _eyeManager.CurrentEye; - var vec = eye.Position.Position; - AL.Listener(ALListener3f.Position, vec.X, vec.Y, -5); - var rot2d = eye.Rotation.ToVec(); - AL.Listener(ALListenerfv.Orientation, new []{0, 0, -1, rot2d.X, rot2d.Y, 0}); - - // Default orientation: at: (0, 0, -1) up: (0, 1, 0) - var rot = eye.Rotation.ToVec(); - var at = new Vector3(0f, 0f, -1f); - var up = new Vector3(rot.Y, rot.X, 0f); - AL.Listener(ALListenerfv.Orientation, ref at, ref up); - - _flushALDisposeQueues(); - } - - private static void RemoveEfx((int sourceHandle, int filterHandle) handles) - { - if (handles.filterHandle != 0) EFX.DeleteFilter(handles.filterHandle); - } - - public void SetMasterVolume(float newVolume) - { - AL.Listener(ALListenerf.Gain, _baseGain * newVolume); - } - - public void SetAudioAttenuation(int value) - { - var attenuation = (Attenuation) value; - - switch (attenuation) - { - case Attenuation.NoAttenuation: - AL.DistanceModel(ALDistanceModel.None); - break; - case Attenuation.InverseDistance: - AL.DistanceModel(ALDistanceModel.InverseDistance); - break; - case Attenuation.Default: - case Attenuation.InverseDistanceClamped: - AL.DistanceModel(ALDistanceModel.InverseDistanceClamped); - break; - case Attenuation.LinearDistance: - AL.DistanceModel(ALDistanceModel.LinearDistance); - break; - case Attenuation.LinearDistanceClamped: - AL.DistanceModel(ALDistanceModel.LinearDistanceClamped); - break; - case Attenuation.ExponentDistance: - AL.DistanceModel(ALDistanceModel.ExponentDistance); - break; - case Attenuation.ExponentDistanceClamped: - AL.DistanceModel(ALDistanceModel.ExponentDistanceClamped); - break; - default: - throw new ArgumentOutOfRangeException($"No implementation to set {attenuation.ToString()} for DistanceModel!"); - } - - var attToString = attenuation == Attenuation.Default ? Attenuation.InverseDistanceClamped : attenuation; - - OpenALSawmill.Info($"Set audio attenuation to {attToString.ToString()}"); - } - - public IClydeAudioSource? CreateAudioSource(AudioStream stream) - { - var source = AL.GenSource(); - - if (!AL.IsSource(source)) - { - OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace); - return null; - } - - // ReSharper disable once PossibleInvalidOperationException - // TODO: This really shouldn't be indexing based on the ClydeHandle... - AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value.Value].BufferHandle); - - var audioSource = new AudioSource(this, source, stream); - _audioSources.Add(source, new WeakReference(audioSource)); - return audioSource; - } - - public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false) - { - var source = AL.GenSource(); - - if (!AL.IsSource(source)) - throw new Exception("Failed to generate source. Too many simultaneous audio streams?"); - - // ReSharper disable once PossibleInvalidOperationException - - var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio); - _bufferedAudioSources.Add(source, new WeakReference(audioSource)); - return audioSource; - } - - private void _checkAlcError(ALDevice device, - [CallerMemberName] string callerMember = "", - [CallerLineNumber] int callerLineNumber = -1) - { - var error = ALC.GetError(device); - if (error != AlcError.NoError) - { - OpenALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error); - } - } - - private void _checkAlError([CallerMemberName] string callerMember = "", - [CallerLineNumber] int callerLineNumber = -1) - { - var error = AL.GetError(); - if (error != ALError.NoError) - { - OpenALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error); - } - } - - public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null) - { - var vorbis = _readOggVorbis(stream); - - var buffer = AL.GenBuffer(); - - ALFormat format; - // NVorbis only supports loading into floats. - // If this becomes a problem due to missing extension support (doubt it but ok), - // check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM. - if (vorbis.Channels == 1) - { - format = ALFormat.MonoFloat32Ext; - } - else if (vorbis.Channels == 2) - { - format = ALFormat.StereoFloat32Ext; - } - else - { - throw new InvalidOperationException("Unable to load audio with more than 2 channels."); - } - - unsafe - { - fixed (float* ptr = vorbis.Data.Span) - { - AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float), - (int) vorbis.SampleRate); - } - } - - _checkAlError(); - - var handle = new ClydeHandle(_audioSampleBuffers.Count); - _audioSampleBuffers.Add(new LoadedAudioSample(buffer)); - var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate); - return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist); - } - - public AudioStream LoadAudioWav(Stream stream, string? name = null) - { - var wav = _readWav(stream); - - var buffer = AL.GenBuffer(); - - ALFormat format; - if (wav.BitsPerSample == 16) - { - if (wav.NumChannels == 1) - { - format = ALFormat.Mono16; - } - else if (wav.NumChannels == 2) - { - format = ALFormat.Stereo16; - } - else - { - throw new InvalidOperationException("Unable to load audio with more than 2 channels."); - } - } - else if (wav.BitsPerSample == 8) - { - if (wav.NumChannels == 1) - { - format = ALFormat.Mono8; - } - else if (wav.NumChannels == 2) - { - format = ALFormat.Stereo8; - } - else - { - throw new InvalidOperationException("Unable to load audio with more than 2 channels."); - } - } - else - { - throw new InvalidOperationException("Unable to load wav with bits per sample different from 8 or 16"); - } - - unsafe - { - fixed (byte* ptr = wav.Data.Span) - { - AL.BufferData(buffer, format, (IntPtr) ptr, wav.Data.Length, wav.SampleRate); - } - } - - _checkAlError(); - - var handle = new ClydeHandle(_audioSampleBuffers.Count); - _audioSampleBuffers.Add(new LoadedAudioSample(buffer)); - var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate); - return new AudioStream(handle, length, wav.NumChannels, name); - } - - public AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null) - { - var fmt = channels switch - { - 1 => ALFormat.Mono16, - 2 => ALFormat.Stereo16, - _ => throw new ArgumentOutOfRangeException( - nameof(channels), "Only stereo and mono is currently supported") - }; - - var buffer = AL.GenBuffer(); - _checkAlError(); - - unsafe - { - fixed (short* ptr = samples) - { - AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate); - } - } - - _checkAlError(); - - var handle = new ClydeHandle(_audioSampleBuffers.Count); - var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate); - _audioSampleBuffers.Add(new LoadedAudioSample(buffer)); - return new AudioStream(handle, length, channels, name); - } - - private sealed class LoadedAudioSample - { - public readonly int BufferHandle; - - public LoadedAudioSample(int bufferHandle) - { - BufferHandle = bufferHandle; - } - } - } -} diff --git a/Robust.Client/Graphics/Audio/ClydeAudioHeadless.cs b/Robust.Client/Graphics/Audio/ClydeAudioHeadless.cs deleted file mode 100644 index c1a2d2952..000000000 --- a/Robust.Client/Graphics/Audio/ClydeAudioHeadless.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Robust.Client.Audio; -using Robust.Client.Input; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Timing; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Color = Robust.Shared.Maths.Color; - -namespace Robust.Client.Graphics.Audio -{ - /// - /// Hey look, it's ClydeAudio's evil twin brother! - /// - [UsedImplicitly] - internal sealed class ClydeAudioHeadless : IClydeAudio, IClydeAudioInternal - { - public bool InitializePostWindowing() - { - return true; - } - - public void FrameProcess(FrameEventArgs eventArgs) - { - } - - public void Shutdown() - { - } - - public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null) - { - // TODO: Might wanna actually load this so the length gets reported correctly. - return new(default, default, 1, name); - } - - public AudioStream LoadAudioWav(Stream stream, string? name = null) - { - // TODO: Might wanna actually load this so the length gets reported correctly. - return new(default, default, 1, name); - } - - public AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null) - { - // TODO: Might wanna actually load this so the length gets reported correctly. - return new(default, default, channels, name); - } - - public IClydeAudioSource CreateAudioSource(AudioStream stream) - { - return DummyAudioSource.Instance; - } - - public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false) - { - return DummyBufferedAudioSource.Instance; - } - - public void SetMasterVolume(float newVolume) - { - // Nada. - } - - public void DisposeAllAudio() - { - // Nada. - } - - public void StopAllAudio() - { - // Nada. - } - } -} diff --git a/Robust.Client/Graphics/Audio/DummyAudioSource.cs b/Robust.Client/Graphics/Audio/DummyAudioSource.cs deleted file mode 100644 index aa706f211..000000000 --- a/Robust.Client/Graphics/Audio/DummyAudioSource.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Numerics; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Robust.Client.Audio; -using Robust.Client.Input; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Timing; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Color = Robust.Shared.Maths.Color; - -namespace Robust.Client.Graphics.Audio -{ - /// - /// Hey look, it's ClydeAudio.AudioSource's evil twin brother! - /// - [Virtual] - internal class DummyAudioSource : IClydeAudioSource - { - public static DummyAudioSource Instance { get; } = new(); - - public bool IsPlaying => default; - public bool IsLooping { get; set; } - - public void Dispose() - { - // Nada. - } - - public void StartPlaying() - { - // Nada. - } - - public void StopPlaying() - { - // Nada. - } - - public bool IsGlobal { get; } - - public bool SetPosition(Vector2 position) - { - return true; - } - - public void SetPitch(float pitch) - { - // Nada. - } - - public void SetGlobal() - { - // Nada. - } - - public void SetVolume(float decibels) - { - // Nada. - } - - public void SetVolumeDirect(float gain) - { - // Nada. - } - - public void SetMaxDistance(float maxDistance) - { - // Nada. - } - - public void SetRolloffFactor(float rolloffFactor) - { - // Nada. - } - - public void SetReferenceDistance(float refDistance) - { - // Nada. - } - - public void SetOcclusion(float blocks) - { - // Nada. - } - - public void SetPlaybackPosition(float seconds) - { - // Nada. - } - - public void SetVelocity(Vector2 velocity) - { - // Nada. - } - } -} diff --git a/Robust.Client/Graphics/Audio/FallbackProxyClydeAudio.cs b/Robust.Client/Graphics/Audio/FallbackProxyClydeAudio.cs deleted file mode 100644 index 98673451b..000000000 --- a/Robust.Client/Graphics/Audio/FallbackProxyClydeAudio.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Robust.Client.Audio; -using Robust.Client.Input; -using Robust.Shared.Timing; -using Robust.Shared.IoC; - -namespace Robust.Client.Graphics.Audio -{ - /// - /// For "start ss14 with no audio devices" Smugleaf - /// - [UsedImplicitly] - internal sealed class FallbackProxyClydeAudio : ProxyClydeAudio - { - [Dependency] private readonly IDependencyCollection _deps = default!; - - public override bool InitializePostWindowing() - { - // Deliberate lack of base call here (see base implementation for comments as to why there even is a base) - - ActualImplementation = new ClydeAudio(); - _deps.InjectDependencies(ActualImplementation, true); - if (ActualImplementation.InitializePostWindowing()) - return true; - - // If we get here, that failed, so use the fallback - ActualImplementation = new ClydeAudioHeadless(); - _deps.InjectDependencies(ActualImplementation, true); - return ActualImplementation.InitializePostWindowing(); - } - } -} diff --git a/Robust.Client/Graphics/Audio/ProxyClydeAudio.cs b/Robust.Client/Graphics/Audio/ProxyClydeAudio.cs deleted file mode 100644 index 79b5eeaf7..000000000 --- a/Robust.Client/Graphics/Audio/ProxyClydeAudio.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Robust.Client.Audio; -using Robust.Client.Input; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Timing; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Color = Robust.Shared.Maths.Color; - -namespace Robust.Client.Graphics.Audio -{ - /// - /// For "start ss14 with no audio devices" Smugleaf - /// - [UsedImplicitly] - internal abstract class ProxyClydeAudio : IClydeAudio, IClydeAudioInternal - { - protected IClydeAudioInternal ActualImplementation = default!; - - public virtual bool InitializePostWindowing() - { - // This particular implementation exists to be overridden because removing this method causes C# to complain - return ActualImplementation.InitializePostWindowing(); - } - - public void FrameProcess(FrameEventArgs eventArgs) - { - ActualImplementation.FrameProcess(eventArgs); - } - - public void Shutdown() - { - ActualImplementation.Shutdown(); - } - - public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null) - { - return ActualImplementation.LoadAudioOggVorbis(stream, name); - } - - public AudioStream LoadAudioWav(Stream stream, string? name = null) - { - return ActualImplementation.LoadAudioWav(stream, name); - } - - public AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null) - { - return ActualImplementation.LoadAudioRaw(samples, channels, sampleRate, name); - } - - public IClydeAudioSource? CreateAudioSource(AudioStream stream) - { - return ActualImplementation.CreateAudioSource(stream); - } - - public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false) - { - return ActualImplementation.CreateBufferedAudioSource(buffers, floatAudio); - } - - public void SetMasterVolume(float newVolume) - { - ActualImplementation.SetMasterVolume(newVolume); - } - - public void DisposeAllAudio() - { - ActualImplementation.DisposeAllAudio(); - } - - public void StopAllAudio() - { - ActualImplementation.StopAllAudio(); - } - } -} diff --git a/Robust.Client/Graphics/Clyde/Clyde.Windowing.cs b/Robust.Client/Graphics/Clyde/Clyde.Windowing.cs index 98ca8b9d9..ebda3975e 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Windowing.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Windowing.cs @@ -260,14 +260,14 @@ namespace Robust.Client.Graphics.Clyde yield break; } - foreach (var file in _resourceCache.ContentFindFiles(_windowIconPath)) + foreach (var file in _resManager.ContentFindFiles(_windowIconPath)) { if (file.Extension != "png") { continue; } - using var stream = _resourceCache.ContentFileRead(file); + using var stream = _resManager.ContentFileRead(file); yield return Image.Load(stream); } } diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs index b85f33b3c..571d2f109 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.cs @@ -11,6 +11,7 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Shared; using Robust.Shared.Configuration; +using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.IoC; @@ -36,6 +37,7 @@ namespace Robust.Client.Graphics.Clyde [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IOverlayManager _overlayManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IResourceManager _resManager = default!; [Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; diff --git a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs index a75ff8de7..972d2c9d1 100644 --- a/Robust.Client/Graphics/Clyde/ClydeHeadless.cs +++ b/Robust.Client/Graphics/Clyde/ClydeHeadless.cs @@ -292,123 +292,6 @@ namespace Robust.Client.Graphics.Clyde } } - [Virtual] - private class DummyAudioSource : IClydeAudioSource - { - public static DummyAudioSource Instance { get; } = new(); - - public bool IsPlaying => default; - public bool IsLooping { get; set; } - - public void Dispose() - { - // Nada. - } - - public void StartPlaying() - { - // Nada. - } - - public void StopPlaying() - { - // Nada. - } - - public bool IsGlobal { get; } - - public bool SetPosition(Vector2 position) - { - return true; - } - - public void SetPitch(float pitch) - { - // Nada. - } - - public void SetGlobal() - { - // Nada. - } - - public void SetVolume(float decibels) - { - // Nada. - } - - public void SetVolumeDirect(float gain) - { - // Nada. - } - - public void SetMaxDistance(float maxDistance) - { - // Nada. - } - - public void SetRolloffFactor(float rolloffFactor) - { - // Nada. - } - - public void SetReferenceDistance(float refDistance) - { - // Nada. - } - - public void SetOcclusion(float blocks) - { - // Nada. - } - - public void SetPlaybackPosition(float seconds) - { - // Nada. - } - - public void SetVelocity(Vector2 velocity) - { - // Nada. - } - } - - private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource - { - public new static DummyBufferedAudioSource Instance { get; } = new(); - public int SampleRate { get; set; } = 0; - - public void WriteBuffer(int handle, ReadOnlySpan data) - { - // Nada. - } - - public void WriteBuffer(int handle, ReadOnlySpan data) - { - // Nada. - } - - public void QueueBuffers(ReadOnlySpan handles) - { - // Nada. - } - - public void EmptyBuffers() - { - // Nada. - } - - public void GetBuffersProcessed(Span handles) - { - // Nada. - } - - public int GetNumberOfBuffersProcessed() - { - return 0; - } - } - private sealed class DummyTexture : OwnedTexture { public DummyTexture(Vector2i size) : base(size) diff --git a/Robust.Client/Graphics/ClydeHandle.cs b/Robust.Client/Graphics/ClydeHandle.cs index 3e7319e82..5dadc3f62 100644 --- a/Robust.Client/Graphics/ClydeHandle.cs +++ b/Robust.Client/Graphics/ClydeHandle.cs @@ -1,54 +1,54 @@ using System; +using Robust.Shared.Graphics; -namespace Robust.Client.Graphics +namespace Robust.Client.Graphics; + +internal readonly struct ClydeHandle : IEquatable, IClydeHandle { - internal struct ClydeHandle : IEquatable + public ClydeHandle(long value) { - public ClydeHandle(long value) - { - Value = value; - } + Value = value; + } - public readonly long Value; + public long Value { get; } - public static explicit operator ClydeHandle(long x) - { - return new(x); - } + public static explicit operator ClydeHandle(long x) + { + return new(x); + } - public static explicit operator long(ClydeHandle h) - { - return h.Value; - } + public static explicit operator long(ClydeHandle h) + { + return h.Value; + } - public bool Equals(ClydeHandle other) - { - return Value == other.Value; - } + public bool Equals(ClydeHandle other) + { + return Value == other.Value; + } - public override bool Equals(object? obj) - { - return obj is ClydeHandle other && Equals(other); - } + public override bool Equals(object? obj) + { + return obj is ClydeHandle other && Equals(other); + } - public override int GetHashCode() - { - return Value.GetHashCode(); - } + public override int GetHashCode() + { + return Value.GetHashCode(); + } - public static bool operator ==(ClydeHandle left, ClydeHandle right) - { - return left.Value == right.Value; - } + public static bool operator ==(ClydeHandle left, ClydeHandle right) + { + return left.Value == right.Value; + } - public static bool operator !=(ClydeHandle left, ClydeHandle right) - { - return left.Value != right.Value; - } + public static bool operator !=(ClydeHandle left, ClydeHandle right) + { + return left.Value != right.Value; + } - public override string ToString() - { - return $"ClydeHandle {Value}"; - } + public override string ToString() + { + return $"ClydeHandle {Value}"; } } diff --git a/Robust.Client/Graphics/Drawing/DrawingHandleScreen.cs b/Robust.Client/Graphics/Drawing/DrawingHandleScreen.cs index 5decd1585..e6192d7af 100644 --- a/Robust.Client/Graphics/Drawing/DrawingHandleScreen.cs +++ b/Robust.Client/Graphics/Drawing/DrawingHandleScreen.cs @@ -114,9 +114,10 @@ namespace Robust.Client.Graphics { if (rune == new Rune('\n')) { - baseLine.X = 0f; baseLine.Y += lineHeight; advanceTotal.Y += lineHeight; + advanceTotal.X = Math.Max(advanceTotal.X, baseLine.X); + baseLine.X = 0f; continue; } @@ -126,7 +127,6 @@ namespace Robust.Client.Graphics continue; var advance = metrics.Value.Advance; - advanceTotal.X += advance; baseLine += new Vector2(advance, 0); } diff --git a/Robust.Client/Graphics/IClydeAudio.cs b/Robust.Client/Graphics/IClydeAudio.cs deleted file mode 100644 index 277c6ac18..000000000 --- a/Robust.Client/Graphics/IClydeAudio.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.IO; -using Robust.Client.Audio; - -namespace Robust.Client.Graphics -{ - public interface IClydeAudio - { - // AUDIO SYSTEM DOWN BELOW. - AudioStream LoadAudioOggVorbis(Stream stream, string? name = null); - AudioStream LoadAudioWav(Stream stream, string? name = null); - AudioStream LoadAudioRaw(ReadOnlySpan samples, int channels, int sampleRate, string? name = null); - - void SetMasterVolume(float newVolume); - - void DisposeAllAudio(); - - void StopAllAudio(); - - IClydeAudioSource? CreateAudioSource(AudioStream stream); - IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false); - } -} diff --git a/Robust.Client/Graphics/IClydeAudioInternal.cs b/Robust.Client/Graphics/IClydeAudioInternal.cs deleted file mode 100644 index df19590a8..000000000 --- a/Robust.Client/Graphics/IClydeAudioInternal.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Robust.Client.Input; -using Robust.Client.UserInterface; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Timing; - -namespace Robust.Client.Graphics -{ - internal interface IClydeAudioInternal : IClydeAudio - { - bool InitializePostWindowing(); - void FrameProcess(FrameEventArgs eventArgs); - void Shutdown(); - } -} diff --git a/Robust.Client/Graphics/IClydeAudioSource.cs b/Robust.Client/Graphics/IClydeAudioSource.cs deleted file mode 100644 index c03b3dd33..000000000 --- a/Robust.Client/Graphics/IClydeAudioSource.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Numerics; -using JetBrains.Annotations; -using Robust.Shared.Maths; - -namespace Robust.Client.Graphics -{ - public interface IClydeAudioSource : IDisposable - { - void StartPlaying(); - void StopPlaying(); - - bool IsPlaying { get; } - - bool IsLooping { get; set; } - bool IsGlobal { get; } - - [MustUseReturnValue] - bool SetPosition(Vector2 position); - void SetPitch(float pitch); - void SetGlobal(); - void SetVolume(float decibels); - void SetVolumeDirect(float gain); - void SetMaxDistance(float maxDistance); - void SetRolloffFactor(float rolloffFactor); - void SetReferenceDistance(float refDistance); - void SetOcclusion(float blocks); - void SetPlaybackPosition(float seconds); - void SetVelocity(Vector2 velocity); - } -} diff --git a/Robust.Client/Graphics/IClydeBufferedAudioSource.cs b/Robust.Client/Graphics/IClydeBufferedAudioSource.cs deleted file mode 100644 index eccda3dc7..000000000 --- a/Robust.Client/Graphics/IClydeBufferedAudioSource.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Robust.Client.Graphics -{ - public interface IClydeBufferedAudioSource : IClydeAudioSource - { - int SampleRate { get; set; } - int GetNumberOfBuffersProcessed(); - void GetBuffersProcessed(Span handles); - void WriteBuffer(int handle, ReadOnlySpan data); - void WriteBuffer(int handle, ReadOnlySpan data); - void QueueBuffers(ReadOnlySpan handles); - void EmptyBuffers(); - } -} diff --git a/Robust.Client/Graphics/Overlays/IOverlayManager.cs b/Robust.Client/Graphics/Overlays/IOverlayManager.cs index 611bae5e1..1c94894e0 100644 --- a/Robust.Client/Graphics/Overlays/IOverlayManager.cs +++ b/Robust.Client/Graphics/Overlays/IOverlayManager.cs @@ -4,32 +4,29 @@ using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Robust.Shared.Timing; -namespace Robust.Client.Graphics +namespace Robust.Client.Graphics; + +[PublicAPI] +public interface IOverlayManager { + bool AddOverlay(Overlay overlay); - [PublicAPI] - public interface IOverlayManager - { - bool AddOverlay(Overlay overlay); + bool RemoveOverlay(Overlay overlay); + bool RemoveOverlay(Type overlayClass); + bool RemoveOverlay() where T : Overlay; + bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay); + bool TryGetOverlay([NotNullWhen(true)] out T? overlay) where T : Overlay; - bool RemoveOverlay(Overlay overlay); - bool RemoveOverlay(Type overlayClass); - bool RemoveOverlay() where T : Overlay; + Overlay GetOverlay(Type overlayClass); + T GetOverlay() where T : Overlay; - bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay); - bool TryGetOverlay([NotNullWhen(true)] out T? overlay) where T : Overlay; + bool HasOverlay(Type overlayClass); + bool HasOverlay() where T : Overlay; - Overlay GetOverlay(Type overlayClass); - T GetOverlay() where T : Overlay; - - bool HasOverlay(Type overlayClass); - bool HasOverlay() where T : Overlay; - - IEnumerable AllOverlays { get; } - } - - internal interface IOverlayManagerInternal : IOverlayManager - { - void FrameUpdate(FrameEventArgs args); - } + IEnumerable AllOverlays { get; } +} + +internal interface IOverlayManagerInternal : IOverlayManager +{ + void FrameUpdate(FrameEventArgs args); } diff --git a/Robust.Client/Graphics/Overlays/OverlayManager.cs b/Robust.Client/Graphics/Overlays/OverlayManager.cs index 5bb32c710..e0022802b 100644 --- a/Robust.Client/Graphics/Overlays/OverlayManager.cs +++ b/Robust.Client/Graphics/Overlays/OverlayManager.cs @@ -6,107 +6,106 @@ using Robust.Shared.Log; using Robust.Shared.Timing; using Robust.Shared.ViewVariables; -namespace Robust.Client.Graphics +namespace Robust.Client.Graphics; + +internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit { - internal sealed class OverlayManager : IOverlayManagerInternal, IPostInjectInit + [Dependency] private readonly ILogManager _logMan = default!; + + [ViewVariables] + private readonly Dictionary _overlays = new Dictionary(); + private ISawmill _logger = default!; + + public IEnumerable AllOverlays => _overlays.Values; + + public void FrameUpdate(FrameEventArgs args) { - [Dependency] private readonly ILogManager _logMan = default!; - - [ViewVariables] - private readonly Dictionary _overlays = new Dictionary(); - private ISawmill _logger = default!; - - public IEnumerable AllOverlays => _overlays.Values; - - public void FrameUpdate(FrameEventArgs args) + foreach (var overlay in _overlays.Values) { - foreach (var overlay in _overlays.Values) - { - overlay.FrameUpdate(args); - } + overlay.FrameUpdate(args); } + } - public bool AddOverlay(Overlay overlay) + public bool AddOverlay(Overlay overlay) + { + if (_overlays.ContainsKey(overlay.GetType())) + return false; + _overlays.Add(overlay.GetType(), overlay); + return true; + } + + public bool RemoveOverlay(Type overlayClass) + { + if (!overlayClass.IsSubclassOf(typeof(Overlay))) { - if (_overlays.ContainsKey(overlay.GetType())) - return false; - _overlays.Add(overlay.GetType(), overlay); - return true; - } - - public bool RemoveOverlay(Type overlayClass) - { - if (!overlayClass.IsSubclassOf(typeof(Overlay))) - { - _logger.Error($"RemoveOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!"); - return false; - } - - return _overlays.Remove(overlayClass); - } - - public bool RemoveOverlay() where T : Overlay - { - return RemoveOverlay(typeof(T)); - } - - public bool RemoveOverlay(Overlay overlay) - { - return _overlays.Remove(overlay.GetType()); - } - - public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay) - { - overlay = null; - if (!overlayClass.IsSubclassOf(typeof(Overlay))) - { - _logger.Error($"TryGetOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!"); - return false; - } - - return _overlays.TryGetValue(overlayClass, out overlay); - } - - public bool TryGetOverlay([NotNullWhen(true)] out T? overlay) where T : Overlay - { - overlay = null; - if (_overlays.TryGetValue(typeof(T), out Overlay? toReturn)) - { - overlay = (T)toReturn; - return true; - } - + _logger.Error($"RemoveOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!"); return false; } - public Overlay GetOverlay(Type overlayClass) + return _overlays.Remove(overlayClass); + } + + public bool RemoveOverlay() where T : Overlay + { + return RemoveOverlay(typeof(T)); + } + + public bool RemoveOverlay(Overlay overlay) + { + return _overlays.Remove(overlay.GetType()); + } + + public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay) + { + overlay = null; + if (!overlayClass.IsSubclassOf(typeof(Overlay))) { - return _overlays[overlayClass]; + _logger.Error($"TryGetOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!"); + return false; } - public T GetOverlay() where T : Overlay + return _overlays.TryGetValue(overlayClass, out overlay); + } + + public bool TryGetOverlay([NotNullWhen(true)] out T? overlay) where T : Overlay + { + overlay = null; + if (_overlays.TryGetValue(typeof(T), out Overlay? toReturn)) { - return (T)_overlays[typeof(T)]; + overlay = (T)toReturn; + return true; } - public bool HasOverlay(Type overlayClass) - { - if (!overlayClass.IsSubclassOf(typeof(Overlay))) - { - _logger.Error($"HasOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!"); - } + return false; + } - return _overlays.ContainsKey(overlayClass); + public Overlay GetOverlay(Type overlayClass) + { + return _overlays[overlayClass]; + } + + public T GetOverlay() where T : Overlay + { + return (T)_overlays[typeof(T)]; + } + + public bool HasOverlay(Type overlayClass) + { + if (!overlayClass.IsSubclassOf(typeof(Overlay))) + { + _logger.Error($"HasOverlay was called with arg: {overlayClass}, which is not a subclass of Overlay!"); } - public bool HasOverlay() where T : Overlay - { - return _overlays.ContainsKey(typeof(T)); - } + return _overlays.ContainsKey(overlayClass); + } - void IPostInjectInit.PostInject() - { - _logger = _logMan.GetSawmill("overlay"); - } + public bool HasOverlay() where T : Overlay + { + return _overlays.ContainsKey(typeof(T)); + } + + void IPostInjectInit.PostInject() + { + _logger = _logMan.GetSawmill("overlay"); } } diff --git a/Robust.Client/Map/ClydeTileDefinitionManager.cs b/Robust.Client/Map/ClydeTileDefinitionManager.cs index ce8081a88..d0dd55655 100644 --- a/Robust.Client/Map/ClydeTileDefinitionManager.cs +++ b/Robust.Client/Map/ClydeTileDefinitionManager.cs @@ -7,6 +7,7 @@ using Robust.Client.Map; using Robust.Client.ResourceManagement; using Robust.Client.Utility; using Robust.Shared.Console; +using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.IoC; @@ -22,7 +23,7 @@ namespace Robust.Client.Map { internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager { - [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IResourceManager _manager = default!; private Texture? _tileTextureAtlas; @@ -86,7 +87,7 @@ namespace Robust.Client.Map 0, (h - EyeManager.PixelsPerMeter) / h, tileSize / w, tileSize / h); Image image; - using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png")) + using (var stream = _manager.ContentFileRead("/Textures/noTile.png")) { image = Image.Load(stream); } @@ -110,7 +111,7 @@ namespace Robust.Client.Map // Already know it's not null above var path = def.Sprite!.Value; - using (var stream = _resourceCache.ContentFileRead(path)) + using (var stream = _manager.ContentFileRead(path)) { image = Image.Load(stream); } diff --git a/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs b/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs index a8df7fe91..4054efe14 100644 --- a/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs +++ b/Robust.Client/Replays/Playback/ReplayPlaybackManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Robust.Client.Audio; using Robust.Client.Audio.Midi; using Robust.Client.Configuration; using Robust.Client.GameObjects; @@ -10,6 +11,7 @@ using Robust.Client.Player; using Robust.Client.Timing; using Robust.Client.Upload; using Robust.Shared; +using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; @@ -28,7 +30,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager [Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IMidiManager _midi = default!; [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly IClydeAudio _clydeAudio = default!; + [Dependency] private readonly IAudioInternal _clydeAudio = default!; [Dependency] private readonly IClientGameTiming _timing = default!; [Dependency] private readonly IClientNetManager _netMan = default!; [Dependency] private readonly IComponentFactory _factory = default!; diff --git a/Robust.Client/ResourceManagement/BaseResource.cs b/Robust.Client/ResourceManagement/BaseResource.cs index 0381c708a..4bc1b30fb 100644 --- a/Robust.Client/ResourceManagement/BaseResource.cs +++ b/Robust.Client/ResourceManagement/BaseResource.cs @@ -1,36 +1,34 @@ -using System; +using System; using System.Threading; +using Robust.Shared.IoC; using Robust.Shared.Utility; -namespace Robust.Client.ResourceManagement +namespace Robust.Client.ResourceManagement; + +/// +/// Base resource for the cache. +/// +public abstract class BaseResource : IDisposable { /// - /// Base resource for the cache. + /// Fallback resource path if this one does not exist. /// - public abstract class BaseResource : IDisposable + public virtual ResPath? Fallback => null; + + /// + /// Disposes this resource. + /// + public virtual void Dispose() { - /// - /// Fallback resource path if this one does not exist. - /// - public virtual ResPath? Fallback => null; + } - /// - /// Disposes this resource. - /// - public virtual void Dispose() - { - } + /// + /// Deserializes the resource from the VFS. + /// + public abstract void Load(IDependencyCollection dependencies, ResPath path); - /// - /// Deserializes the resource from the VFS. - /// - /// ResourceCache this resource is being loaded into. - /// Path of the resource requested on the VFS. - public abstract void Load(IResourceCache cache, ResPath path); + public virtual void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default) + { - public virtual void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default) - { - - } } } diff --git a/Robust.Client/ResourceManagement/IResourceCache.cs b/Robust.Client/ResourceManagement/IResourceCache.cs index 7b9b3a98a..a33daf48c 100644 --- a/Robust.Client/ResourceManagement/IResourceCache.cs +++ b/Robust.Client/ResourceManagement/IResourceCache.cs @@ -1,49 +1,51 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Robust.Client.Graphics; using Robust.Shared.ContentPack; using Robust.Shared.Utility; -namespace Robust.Client.ResourceManagement +namespace Robust.Client.ResourceManagement; + +/// +/// Handles caching of +/// +public interface IResourceCache : IResourceManager { - public interface IResourceCache : IResourceManager - { - T GetResource(string path, bool useFallback = true) - where T : BaseResource, new(); + T GetResource(string path, bool useFallback = true) + where T : BaseResource, new(); - T GetResource(ResPath path, bool useFallback = true) - where T : BaseResource, new(); + T GetResource(ResPath path, bool useFallback = true) + where T : BaseResource, new(); - bool TryGetResource(string path, [NotNullWhen(true)] out T? resource) - where T : BaseResource, new(); + bool TryGetResource(string path, [NotNullWhen(true)] out T? resource) + where T : BaseResource, new(); - bool TryGetResource(ResPath path, [NotNullWhen(true)] out T? resource) - where T : BaseResource, new(); + bool TryGetResource(ResPath path, [NotNullWhen(true)] out T? resource) + where T : BaseResource, new(); - void ReloadResource(string path) - where T : BaseResource, new(); + void ReloadResource(string path) + where T : BaseResource, new(); - void ReloadResource(ResPath path) - where T : BaseResource, new(); + void ReloadResource(ResPath path) + where T : BaseResource, new(); - void CacheResource(string path, T resource) - where T : BaseResource, new(); + void CacheResource(string path, T resource) + where T : BaseResource, new(); - void CacheResource(ResPath path, T resource) - where T : BaseResource, new(); + void CacheResource(ResPath path, T resource) + where T : BaseResource, new(); - T GetFallback() - where T : BaseResource, new(); + T GetFallback() + where T : BaseResource, new(); - IEnumerable> GetAllResources() where T : BaseResource, new(); + IEnumerable> GetAllResources() where T : BaseResource, new(); - // Resource load callbacks so content can hook stuff like click maps. - event Action OnRawTextureLoaded; - event Action OnRsiLoaded; - - IClyde Clyde { get; } - IClydeAudio ClydeAudio { get; } - IFontManager FontManager { get; } - } + // Resource load callbacks so content can hook stuff like click maps. + event Action OnRawTextureLoaded; + event Action OnRsiLoaded; + + IClyde Clyde { get; } + IFontManager FontManager { get; } } + diff --git a/Robust.Client/ResourceManagement/IResourceCacheInternal.cs b/Robust.Client/ResourceManagement/IResourceCacheInternal.cs index ed78d53d4..7d74ec77c 100644 --- a/Robust.Client/ResourceManagement/IResourceCacheInternal.cs +++ b/Robust.Client/ResourceManagement/IResourceCacheInternal.cs @@ -2,14 +2,14 @@ using Robust.Shared.ContentPack; using Robust.Shared.Utility; -namespace Robust.Client.ResourceManagement -{ - internal interface IResourceCacheInternal : IResourceCache, IResourceManagerInternal - { - void TextureLoaded(TextureLoadedEventArgs eventArgs); - void RsiLoaded(RsiLoadedEventArgs eventArgs); +namespace Robust.Client.ResourceManagement; - void MountLoaderApi(IFileApi api, string apiPrefix, ResPath? prefix=null); - void PreloadTextures(); - } +/// +internal interface IResourceCacheInternal : IResourceCache +{ + void TextureLoaded(TextureLoadedEventArgs eventArgs); + void RsiLoaded(RsiLoadedEventArgs eventArgs); + void PreloadTextures(); + + void MountLoaderApi(IResourceManager manager, IFileApi api, string apiPrefix, ResPath? prefix = null); } diff --git a/Robust.Client/ResourceManagement/ResourceCache.LoaderApi.cs b/Robust.Client/ResourceManagement/ResourceCache.LoaderApi.cs index 6bfbf6af9..ca60ad613 100644 --- a/Robust.Client/ResourceManagement/ResourceCache.LoaderApi.cs +++ b/Robust.Client/ResourceManagement/ResourceCache.LoaderApi.cs @@ -9,6 +9,13 @@ namespace Robust.Client.ResourceManagement { internal partial class ResourceCache { + public void MountLoaderApi(IResourceManager manager, IFileApi api, string apiPrefix, ResPath? prefix = null) + { + prefix ??= ResPath.Root; + var root = new LoaderApiLoader(api, apiPrefix); + manager.AddRoot(prefix.Value, root); + } + private sealed class LoaderApiLoader : IContentRoot { private readonly IFileApi _api; diff --git a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs index 90a526a1a..f698171ad 100644 --- a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs +++ b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs @@ -3,10 +3,13 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using OpenToolkit.Graphics.OpenGL4; +using Robust.Client.Audio; using Robust.Client.Graphics; using Robust.Client.Utility; using Robust.Shared; +using Robust.Shared.Audio; using Robust.Shared.Configuration; +using Robust.Shared.ContentPack; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Maths; @@ -19,7 +22,8 @@ namespace Robust.Client.ResourceManagement internal partial class ResourceCache { [field: Dependency] public IClyde Clyde { get; } = default!; - [field: Dependency] public IClydeAudio ClydeAudio { get; } = default!; + [field: Dependency] public IAudioInternal ClydeAudio { get; } = default!; + [Dependency] private readonly IResourceManager _manager = default!; [field: Dependency] public IFontManager FontManager { get; } = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; @@ -44,7 +48,7 @@ namespace Robust.Client.ResourceManagement var sw = Stopwatch.StartNew(); var resList = GetTypeDict(); - var texList = ContentFindFiles("/Textures/") + var texList = _manager.ContentFindFiles("/Textures/") // Skip PNG files inside RSIs. .Where(p => p.Extension == "png" && !p.ToString().Contains(".rsi/") && !resList.ContainsKey(p)) .Select(p => new TextureResource.LoadStepData {Path = p}) @@ -54,7 +58,7 @@ namespace Robust.Client.ResourceManagement { try { - TextureResource.LoadPreTexture(this, data); + TextureResource.LoadPreTexture(_manager, data); } catch (Exception e) { @@ -116,7 +120,7 @@ namespace Robust.Client.ResourceManagement var sw = Stopwatch.StartNew(); var resList = GetTypeDict(); - var rsiList = ContentFindFiles("/Textures/") + var rsiList = _manager.ContentFindFiles("/Textures/") .Where(p => p.ToString().EndsWith(".rsi/meta.json")) .Select(c => c.Directory) .Where(p => !resList.ContainsKey(p)) @@ -127,7 +131,7 @@ namespace Robust.Client.ResourceManagement { try { - RSIResource.LoadPreTexture(this, data); + RSIResource.LoadPreTexture(_manager, data); } catch (Exception e) { diff --git a/Robust.Client/ResourceManagement/ResourceCache.cs b/Robust.Client/ResourceManagement/ResourceCache.cs index 8e3de7075..2bd296f4c 100644 --- a/Robust.Client/ResourceManagement/ResourceCache.cs +++ b/Robust.Client/ResourceManagement/ResourceCache.cs @@ -1,221 +1,219 @@ -using Robust.Shared.ContentPack; -using Robust.Shared.Log; -using Robust.Shared.Utility; -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; -using Robust.LoaderApi; +using Robust.Shared.ContentPack; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Utility; -namespace Robust.Client.ResourceManagement +namespace Robust.Client.ResourceManagement; + +/// +/// Handles caching of +/// +internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable { - internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable + private readonly Dictionary> _cachedResources = + new(); + + private readonly Dictionary _fallbacks = new(); + + public T GetResource(string path, bool useFallback = true) where T : BaseResource, new() { - private readonly Dictionary> CachedResources = - new(); + return GetResource(new ResPath(path), useFallback); + } - private readonly Dictionary _fallbacks = new(); - - public T GetResource(string path, bool useFallback = true) where T : BaseResource, new() + public T GetResource(ResPath path, bool useFallback = true) where T : BaseResource, new() + { + var cache = GetTypeDict(); + if (cache.TryGetValue(path, out var cached)) { - return GetResource(new ResPath(path), useFallback); + return (T) cached; } - public T GetResource(ResPath path, bool useFallback = true) where T : BaseResource, new() + var resource = new T(); + try { - var cache = GetTypeDict(); - if (cache.TryGetValue(path, out var cached)) - { - return (T) cached; - } - - var _resource = new T(); - try - { - _resource.Load(this, path); - cache[path] = _resource; - return _resource; - } - catch (Exception e) - { - if (useFallback && _resource.Fallback != null) - { - Logger.Error( - $"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}"); - return GetResource(_resource.Fallback.Value, false); - } - else - { - Logger.Error( - $"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}"); - throw; - } - } + var dependencies = IoCManager.Instance!; + resource.Load(dependencies, path); + cache[path] = resource; + return resource; } - - public bool TryGetResource(string path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new() + catch (Exception e) { - return TryGetResource(new ResPath(path), out resource); - } - - public bool TryGetResource(ResPath path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new() - { - var cache = GetTypeDict(); - if (cache.TryGetValue(path, out var cached)) + if (useFallback && resource.Fallback != null) { - resource = (T) cached; - return true; + Logger.Error( + $"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}"); + return GetResource(resource.Fallback.Value, false); } - - var _resource = new T(); - try + else { - _resource.Load(this, path); - resource = _resource; - cache[path] = resource; - return true; - } - catch - { - resource = null; - return false; - } - } - - public void ReloadResource(string path) where T : BaseResource, new() - { - ReloadResource(new ResPath(path)); - } - - public void ReloadResource(ResPath path) where T : BaseResource, new() - { - var cache = GetTypeDict(); - - if (!cache.TryGetValue(path, out var res)) - { - return; - } - - try - { - res.Reload(this, path); - } - catch (Exception e) - { - Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}"); + Logger.Error( + $"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}"); throw; } } + } - public bool HasResource(string path) where T : BaseResource, new() + public bool TryGetResource(string path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new() + { + return TryGetResource(new ResPath(path), out resource); + } + + public bool TryGetResource(ResPath path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new() + { + var cache = GetTypeDict(); + if (cache.TryGetValue(path, out var cached)) { - return HasResource(new ResPath(path)); + resource = (T) cached; + return true; } - public bool HasResource(ResPath path) where T : BaseResource, new() + var _resource = new T(); + try { - return TryGetResource(path, out var _); + var dependencies = IoCManager.Instance!; + _resource.Load(dependencies, path); + resource = _resource; + cache[path] = resource; + return true; + } + catch + { + resource = null; + return false; + } + } + + public void ReloadResource(string path) where T : BaseResource, new() + { + ReloadResource(new ResPath(path)); + } + + public void ReloadResource(ResPath path) where T : BaseResource, new() + { + var cache = GetTypeDict(); + + if (!cache.TryGetValue(path, out var res)) + { + return; } - public void CacheResource(string path, T resource) where T : BaseResource, new() + try { - CacheResource(new ResPath(path), resource); + var dependencies = IoCManager.Instance!; + res.Reload(dependencies, path); } - - public void CacheResource(ResPath path, T resource) where T : BaseResource, new() + catch (Exception e) { - GetTypeDict()[path] = resource; + Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}"); + throw; } + } - public T GetFallback() where T : BaseResource, new() + public bool HasResource(string path) where T : BaseResource, new() + { + return HasResource(new ResPath(path)); + } + + public bool HasResource(ResPath path) where T : BaseResource, new() + { + return TryGetResource(path, out var _); + } + + public void CacheResource(string path, T resource) where T : BaseResource, new() + { + CacheResource(new ResPath(path), resource); + } + + public void CacheResource(ResPath path, T resource) where T : BaseResource, new() + { + GetTypeDict()[path] = resource; + } + + public T GetFallback() where T : BaseResource, new() + { + if (_fallbacks.TryGetValue(typeof(T), out var fallback)) { - if (_fallbacks.TryGetValue(typeof(T), out var fallback)) - { - return (T) fallback; - } - - var res = new T(); - if (res.Fallback == null) - { - throw new InvalidOperationException($"Resource of type '{typeof(T)}' has no fallback."); - } - - fallback = GetResource(res.Fallback.Value, useFallback: false); - _fallbacks.Add(typeof(T), fallback); return (T) fallback; } - public IEnumerable> GetAllResources() where T : BaseResource, new() + var res = new T(); + if (res.Fallback == null) { - return GetTypeDict().Select(p => new KeyValuePair(p.Key, (T) p.Value)); + throw new InvalidOperationException($"Resource of type '{typeof(T)}' has no fallback."); } - public event Action? OnRawTextureLoaded; - public event Action? OnRsiLoaded; + fallback = GetResource(res.Fallback.Value, useFallback: false); + _fallbacks.Add(typeof(T), fallback); + return (T) fallback; + } - #region IDisposable Members + public IEnumerable> GetAllResources() where T : BaseResource, new() + { + return GetTypeDict().Select(p => new KeyValuePair(p.Key, (T) p.Value)); + } - private bool disposed = false; + public event Action? OnRawTextureLoaded; + public event Action? OnRsiLoaded; - public void Dispose() + #region IDisposable Members + + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposed) { - Dispose(true); - GC.SuppressFinalize(this); + return; } - private void Dispose(bool disposing) + if (disposing) { - if (disposed) + foreach (var res in _cachedResources.Values.SelectMany(dict => dict.Values)) { - return; + res.Dispose(); } - - if (disposing) - { - foreach (var res in CachedResources.Values.SelectMany(dict => dict.Values)) - { - res.Dispose(); - } - } - - disposed = true; } - ~ResourceCache() + disposed = true; + } + + ~ResourceCache() + { + Dispose(false); + } + + #endregion IDisposable Members + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected Dictionary GetTypeDict() + { + if (!_cachedResources.TryGetValue(typeof(T), out var ret)) { - Dispose(false); + ret = new Dictionary(); + _cachedResources.Add(typeof(T), ret); } - #endregion IDisposable Members + return ret; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Dictionary GetTypeDict() - { - if (!CachedResources.TryGetValue(typeof(T), out var ret)) - { - ret = new Dictionary(); - CachedResources.Add(typeof(T), ret); - } + public void TextureLoaded(TextureLoadedEventArgs eventArgs) + { + OnRawTextureLoaded?.Invoke(eventArgs); + } - return ret; - } - - public void TextureLoaded(TextureLoadedEventArgs eventArgs) - { - OnRawTextureLoaded?.Invoke(eventArgs); - } - - public void RsiLoaded(RsiLoadedEventArgs eventArgs) - { - OnRsiLoaded?.Invoke(eventArgs); - } - - public void MountLoaderApi(IFileApi api, string apiPrefix, ResPath? prefix=null) - { - prefix ??= ResPath.Root; - var root = new LoaderApiLoader(api, apiPrefix); - AddRoot(prefix.Value, root); - } + public void RsiLoaded(RsiLoadedEventArgs eventArgs) + { + OnRsiLoaded?.Invoke(eventArgs); } } diff --git a/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs index 110a7c525..c80665e98 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/AudioResource.cs @@ -1,43 +1,46 @@ -using System; -using Robust.Client.Audio; -using Robust.Shared.Utility; +using System; using System.IO; -using Robust.Client.Graphics; +using Robust.Client.Audio; +using Robust.Shared.Audio; +using Robust.Shared.ContentPack; using Robust.Shared.IoC; +using Robust.Shared.Utility; -namespace Robust.Client.ResourceManagement +namespace Robust.Client.ResourceManagement; + +public sealed class AudioResource : BaseResource { - public sealed class AudioResource : BaseResource + public AudioStream AudioStream { get; private set; } = default!; + + public override void Load(IDependencyCollection dependencies, ResPath path) { - public AudioStream AudioStream { get; private set; } = default!; + var cache = dependencies.Resolve(); - public override void Load(IResourceCache cache, ResPath path) + if (!cache.ContentFileExists(path)) { - if (!cache.ContentFileExists(path)) - { - throw new FileNotFoundException("Content file does not exist for audio sample."); - } - - using (var fileStream = cache.ContentFileRead(path)) - { - if (path.Extension == "ogg") - { - AudioStream = cache.ClydeAudio.LoadAudioOggVorbis(fileStream, path.ToString()); - } - else if (path.Extension == "wav") - { - AudioStream = cache.ClydeAudio.LoadAudioWav(fileStream, path.ToString()); - } - else - { - throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav"); - } - } + throw new FileNotFoundException("Content file does not exist for audio sample."); } - public static implicit operator AudioStream(AudioResource res) + using (var fileStream = cache.ContentFileRead(path)) { - return res.AudioStream; + var audioManager = dependencies.Resolve(); + if (path.Extension == "ogg") + { + AudioStream = audioManager.LoadAudioOggVorbis(fileStream, path.ToString()); + } + else if (path.Extension == "wav") + { + AudioStream = audioManager.LoadAudioWav(fileStream, path.ToString()); + } + else + { + throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav"); + } } } + + public static implicit operator AudioStream(AudioResource res) + { + return res.AudioStream; + } } diff --git a/Robust.Client/ResourceManagement/ResourceTypes/FontResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/FontResource.cs index 8f5705421..2c3cd6dcc 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/FontResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/FontResource.cs @@ -1,5 +1,6 @@ using System.IO; using Robust.Client.Graphics; +using Robust.Shared.ContentPack; using Robust.Shared.IoC; using Robust.Shared.Utility; @@ -9,16 +10,17 @@ namespace Robust.Client.ResourceManagement { internal IFontFaceHandle FontFaceHandle { get; private set; } = default!; - public override void Load(IResourceCache cache, ResPath path) + public override void Load(IDependencyCollection dependencies, ResPath path) { - if (!cache.TryContentFileRead(path, out var stream)) + + if (!dependencies.Resolve().TryContentFileRead(path, out var stream)) { throw new FileNotFoundException("Content file does not exist for font"); } using (stream) { - FontFaceHandle = ((IFontManagerInternal)cache.FontManager).Load(stream); + FontFaceHandle = dependencies.Resolve().Load(stream); } } diff --git a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs index c7cb919f1..f207b5832 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Robust.Client.Graphics; using Robust.Client.Utility; +using Robust.Shared.ContentPack; using Robust.Shared.Graphics; using Robust.Shared.Graphics.RSI; using Robust.Shared.IoC; @@ -34,26 +35,27 @@ namespace Robust.Client.ResourceManagement /// public const uint MAXIMUM_RSI_VERSION = RsiLoading.MAXIMUM_RSI_VERSION; - public override void Load(IResourceCache cache, ResPath path) + public override void Load(IDependencyCollection dependencies, ResPath path) { var loadStepData = new LoadStepData {Path = path}; - LoadPreTexture(cache, loadStepData); + var manager = dependencies.Resolve(); + LoadPreTexture(manager, loadStepData); - loadStepData.AtlasTexture = cache.Clyde.LoadTextureFromImage( + loadStepData.AtlasTexture = dependencies.Resolve().LoadTextureFromImage( loadStepData.AtlasSheet, loadStepData.Path.ToString()); LoadPostTexture(loadStepData); - LoadFinish(cache, loadStepData); + LoadFinish(dependencies.Resolve(), loadStepData); loadStepData.AtlasSheet.Dispose(); } - internal static void LoadPreTexture(IResourceCache cache, LoadStepData data) + internal static void LoadPreTexture(IResourceManager manager, LoadStepData data) { var manifestPath = data.Path / "meta.json"; RsiLoading.RsiMetadata metadata; - using (var manifestFile = cache.ContentFileRead(manifestPath)) + using (var manifestFile = manager.ContentFileRead(manifestPath)) { metadata = RsiLoading.LoadRsiMetadata(manifestFile); } @@ -86,7 +88,7 @@ namespace Robust.Client.ResourceManagement var stateObject = metadata.States[index]; // Load image from disk. var texPath = data.Path / (stateObject.StateId + ".png"); - using (var stream = cache.ContentFileRead(texPath)) + using (var stream = manager.ContentFileRead(texPath)) { reg.Src = Image.Load(stream); } @@ -212,14 +214,10 @@ namespace Robust.Client.ResourceManagement } } - internal void LoadFinish(IResourceCache cache, LoadStepData data) + internal void LoadFinish(IResourceCacheInternal cache, LoadStepData data) { RSI = data.Rsi; - - if (cache is IResourceCacheInternal cacheInternal) - { - cacheInternal.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets)); - } + cache.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets)); } /// diff --git a/Robust.Client/ResourceManagement/ResourceTypes/ShaderSourceResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/ShaderSourceResource.cs index 5d5df20b1..348f34677 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/ShaderSourceResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/ShaderSourceResource.cs @@ -20,28 +20,31 @@ namespace Robust.Client.ResourceManagement [ViewVariables] internal ParsedShader ParsedShader { get; private set; } = default!; - public override void Load(IResourceCache cache, ResPath path) + public override void Load(IDependencyCollection dependencies, ResPath path) { - using (var stream = cache.ContentFileRead(path)) + var manager = dependencies.Resolve(); + + using (var stream = manager.ContentFileRead(path)) using (var reader = new StreamReader(stream, EncodingHelpers.UTF8)) { - ParsedShader = ShaderParser.Parse(reader, cache); + ParsedShader = ShaderParser.Parse(reader, manager); } - ClydeHandle = ((IClydeInternal)cache.Clyde).LoadShader(ParsedShader, path.ToString()); + ClydeHandle = dependencies.Resolve().LoadShader(ParsedShader, path.ToString()); } - public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default) + public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default) { + var manager = dependencies.Resolve(); ct = ct != default ? ct : new CancellationTokenSource(30000).Token; for (;;) { try { - using var stream = cache.ContentFileRead(path); + using var stream = manager.ContentFileRead(path); using var reader = new StreamReader(stream, EncodingHelpers.UTF8); - ParsedShader = ShaderParser.Parse(reader, cache); + ParsedShader = ShaderParser.Parse(reader, manager); break; } catch (IOException ioe) @@ -57,7 +60,7 @@ namespace Robust.Client.ResourceManagement } } - ((IClydeInternal)cache.Clyde).ReloadShader(ClydeHandle, ParsedShader); + dependencies.Resolve().ReloadShader(ClydeHandle, ParsedShader); } } } diff --git a/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs index 24db71b92..69dc11475 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading; using Robust.Client.Graphics; +using Robust.Shared.ContentPack; using Robust.Shared.Graphics; using Robust.Shared.IoC; using Robust.Shared.Log; @@ -19,7 +20,7 @@ namespace Robust.Client.ResourceManagement public Texture Texture => _texture; - public override void Load(IResourceCache cache, ResPath path) + public override void Load(IDependencyCollection dependencies, ResPath path) { if (path.Directory.Filename.EndsWith(".rsi")) { @@ -31,12 +32,12 @@ namespace Robust.Client.ResourceManagement var data = new LoadStepData {Path = path}; - LoadPreTexture(cache, data); - LoadTexture(cache.Clyde, data); - LoadFinish(cache, data); + LoadPreTexture(dependencies.Resolve(), data); + LoadTexture(dependencies.Resolve(), data); + LoadFinish(dependencies.Resolve(), data); } - internal static void LoadPreTexture(IResourceCache cache, LoadStepData data) + internal static void LoadPreTexture(IResourceManager cache, LoadStepData data) { using (var stream = cache.ContentFileRead(data.Path)) { @@ -63,7 +64,7 @@ namespace Robust.Client.ResourceManagement data.Image.Dispose(); } - private static TextureLoadParameters? TryLoadTextureParameters(IResourceCache cache, ResPath path) + private static TextureLoadParameters? TryLoadTextureParameters(IResourceManager cache, ResPath path) { var metaPath = path.WithName(path.Filename + ".yml"); if (cache.TryContentFileRead(metaPath, out var stream)) @@ -90,12 +91,11 @@ namespace Robust.Client.ResourceManagement return null; } - public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default) + public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default) { - var clyde = IoCManager.Resolve(); - var data = new LoadStepData {Path = path}; - LoadPreTexture(cache, data); + + LoadPreTexture(dependencies.Resolve(), data); if (data.Image.Width == Texture.Width && data.Image.Height == Texture.Height) { @@ -106,7 +106,7 @@ namespace Robust.Client.ResourceManagement { // Dimensions do not match, make new texture. _texture.Dispose(); - LoadTexture(clyde, data); + LoadTexture(dependencies.Resolve(), data); _texture = data.Texture; } diff --git a/Robust.Client/Robust.Client.csproj b/Robust.Client/Robust.Client.csproj index 57be10f07..1cbb20d81 100644 --- a/Robust.Client/Robust.Client.csproj +++ b/Robust.Client/Robust.Client.csproj @@ -16,12 +16,12 @@ - + diff --git a/Robust.Client/UserInterface/Themes/UiTheme.cs b/Robust.Client/UserInterface/Themes/UiTheme.cs index 872f4309e..0c14bc3c1 100644 --- a/Robust.Client/UserInterface/Themes/UiTheme.cs +++ b/Robust.Client/UserInterface/Themes/UiTheme.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; +using Robust.Shared.ContentPack; using Robust.Shared.Graphics; using Robust.Shared.IoC; using Robust.Shared.Log; @@ -37,10 +38,11 @@ public sealed class UITheme : IPrototype public Dictionary? Colors { get; } public ResPath Path => _path == default ? new ResPath(DefaultPath+"/"+ID) : _path; - private void ValidateFilePath(IResourceCache resourceCache) + private void ValidateFilePath(IResourceManager manager) { - var foundFolders = resourceCache.ContentFindFiles(Path.ToRootedPath()); - if (!foundFolders.Any()) throw new Exception("UITheme: "+ID+" not found in resources!"); + var foundFolders = manager.ContentFindFiles(Path.ToRootedPath()); + if (!foundFolders.Any()) + throw new Exception("UITheme: "+ID+" not found in resources!"); } public Texture ResolveTexture(string texturePath) diff --git a/Robust.Packaging/AssetProcessing/Passes/AssetPassAudioMetadata.cs b/Robust.Packaging/AssetProcessing/Passes/AssetPassAudioMetadata.cs new file mode 100644 index 000000000..790aaeac6 --- /dev/null +++ b/Robust.Packaging/AssetProcessing/Passes/AssetPassAudioMetadata.cs @@ -0,0 +1,83 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Robust.Shared.Audio; +using Robust.Shared.Audio.AudioLoading; +using Robust.Shared.Serialization; +using YamlDotNet.Core; +using YamlDotNet.RepresentationModel; + +namespace Robust.Packaging.AssetProcessing.Passes; + +/// +/// Strips out audio files and writes them to a metadata .yml +/// Used for server packaging to avoid bundling entire audio files on the server. +/// +public sealed class AssetPassAudioMetadata : AssetPass +{ + private readonly List _audioMetadata = new(); + private readonly string _metadataPath; + + public AssetPassAudioMetadata(string metadataPath = "Prototypes/_audio_metadata.yml") + { + _metadataPath = metadataPath; + } + + protected override AssetFileAcceptResult AcceptFile(AssetFile file) + { + if (!AudioLoader.IsLoadableAudioFile(file.Path)) + return AssetFileAcceptResult.Pass; + + using var stream = file.Open(); + var metadata = AudioLoader.LoadAudioMetadata(stream, file.Path); + + lock (_audioMetadata) + { + _audioMetadata.Add(new AudioMetadataPrototype() + { + ID = "/" + file.Path, + Length = metadata.Length, + }); + } + + return AssetFileAcceptResult.Consumed; + } + + [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] + protected override void AcceptFinished() + { + if (_audioMetadata.Count == 0) + { + Logger?.Debug("Have no audio metadata, not writing anything"); + return; + } + + Logger?.Debug("Writing audio metadata for {0} audio files", _audioMetadata.Count); + + // ReSharper disable once InconsistentlySynchronizedField + var root = new YamlSequenceNode(); + var document = new YamlDocument(root); + + foreach (var prototype in _audioMetadata) + { + // TODO: I know but sermanager and please get me out of this hell. + var jaml = new YamlMappingNode + { + { "type", AudioMetadataPrototype.ProtoName }, + { "id", new YamlScalarNode(prototype.ID) }, + { "length", new YamlScalarNode(prototype.Length.TotalSeconds.ToString(CultureInfo.InvariantCulture)) } + }; + root.Add(jaml); + } + + RunJob(() => + { + using var memory = new MemoryStream(); + using var writer = new StreamWriter(memory); + var yamlStream = new YamlStream(document); + yamlStream.Save(new YamlNoDocEndDotsFix(new YamlMappingFix(new Emitter(writer))), false); + writer.Flush(); + var result = new AssetFileMemory(_metadataPath, memory.ToArray()); + SendFile(result); + }); + } +} diff --git a/Robust.Packaging/AssetProcessing/Passes/AssetPassPrefix.cs b/Robust.Packaging/AssetProcessing/Passes/AssetPassPrefix.cs new file mode 100644 index 000000000..595168ff6 --- /dev/null +++ b/Robust.Packaging/AssetProcessing/Passes/AssetPassPrefix.cs @@ -0,0 +1,28 @@ +namespace Robust.Packaging.AssetProcessing.Passes; + +/// +/// Appends a prefix to file paths of passed-through files. +/// +public sealed class AssetPassPrefix : AssetPass +{ + public string Prefix { get; set; } + + public AssetPassPrefix(string prefix) + { + Prefix = prefix; + } + + protected override AssetFileAcceptResult AcceptFile(AssetFile file) + { + var newPath = Prefix + file.Path; + var newFile = file switch + { + AssetFileDisk disk => (AssetFile) new AssetFileDisk(newPath, disk.DiskPath), + AssetFileMemory memory => new AssetFileMemory(newPath, memory.Memory), + _ => throw new ArgumentOutOfRangeException(nameof(file)) + }; + + SendFile(newFile); + return AssetFileAcceptResult.Consumed; + } +} diff --git a/Robust.Packaging/Robust.Packaging.csproj b/Robust.Packaging/Robust.Packaging.csproj index d6ab2e72e..06cdf2ec8 100644 --- a/Robust.Packaging/Robust.Packaging.csproj +++ b/Robust.Packaging/Robust.Packaging.csproj @@ -10,5 +10,9 @@ + + + + diff --git a/Robust.Packaging/RobustClientAssetGraph.cs b/Robust.Packaging/RobustClientAssetGraph.cs index 85a2eca90..4a7ebfb8e 100644 --- a/Robust.Packaging/RobustClientAssetGraph.cs +++ b/Robust.Packaging/RobustClientAssetGraph.cs @@ -21,6 +21,7 @@ public sealed class RobustClientAssetGraph /// public IReadOnlyCollection AllPasses { get; } + /// Should inputs be run in parallel. Should only be turned off for debugging. public RobustClientAssetGraph(bool parallel = true) { // The code injecting the list of source files is assumed to be pretty single-threaded. @@ -40,7 +41,7 @@ public sealed class RobustClientAssetGraph Input, PresetPasses, Output, - NormalizeText + NormalizeText, }; } } diff --git a/Robust.Packaging/RobustServerAssetGraph.cs b/Robust.Packaging/RobustServerAssetGraph.cs new file mode 100644 index 000000000..ac1bf5119 --- /dev/null +++ b/Robust.Packaging/RobustServerAssetGraph.cs @@ -0,0 +1,129 @@ +using Robust.Packaging.AssetProcessing; +using Robust.Packaging.AssetProcessing.Passes; + +namespace Robust.Packaging; + +/// +/// Standard asset graph for packaging server files. Extend by wiring things up to , , and . +/// +/// +/// +/// This graph has two inputs: one for "core" server files such as the main engine executable, and another for resource files. +/// +/// +/// If you want to add extra passes to run before preset passes, depend them on the relevant input pass, with a before of the relevant preset pass. +/// +/// +/// See the following graph (Mermaid syntax) to understand this asset graph: +/// +/// +/// flowchart LR +/// InputCore --> PresetPassesCore +/// PresetPassesCore --1--> NormalizeTextCore +/// NormalizeTextCore --> Output +/// PresetPassesCore --2--> Output +/// InputResources --> PresetPassesResources +/// PresetPassesResources --1--> AudioMetadata +/// PresetPassesResources --2--> NormalizeTextResources +/// PresetPassesResources --3--> PrefixResources +/// AudioMetadata --> PrefixResources +/// NormalizeTextResources --> PrefixResources +/// PrefixResources --> Output +/// +/// +public sealed class RobustServerAssetGraph +{ + public AssetPassPipe Output { get; } + + /// + /// Input pass for core server files, such as Robust.Server.exe. + /// + /// + public AssetPassPipe InputCore { get; } + + public AssetPassPipe PresetPassesCore { get; } + + /// + /// Normalizes text files in core files. + /// + public AssetPassNormalizeText NormalizeTextCore { get; } + + /// + /// Input pass for server resource files. Everything that will go into Resources/. + /// + /// + /// Do not prefix file paths with Resources/, the asset pass will automatically remap them. + /// + /// + public AssetPassPipe InputResources { get; } + public AssetPassPipe PresetPassesResources { get; } + public AssetPassAudioMetadata AudioMetadata { get; } + + /// + /// Normalizes text files in resources. + /// + public AssetPassNormalizeText NormalizeTextResources { get; } + + /// + /// Responsible for putting resources into the "Resources/" folder. + /// + public AssetPassPrefix PrefixResources { get; } + + /// + /// Collection of all passes in this preset graph. + /// + public IReadOnlyCollection AllPasses { get; } + + /// Should inputs be run in parallel. Should only be turned off for debugging. + public RobustServerAssetGraph(bool parallel = true) + { + Output = new AssetPassPipe { Name = "RobustServerAssetGraphOutput", CheckDuplicates = true }; + + // + // Core files + // + + // The code injecting the list of source files is assumed to be pretty single-threaded. + // We use a parallelizing input to break out all the work on files coming in onto multiple threads. + InputCore = new AssetPassPipe { Name = "RobustServerAssetGraphInputCore", Parallelize = parallel }; + PresetPassesCore = new AssetPassPipe { Name = "RobustServerAssetGraphPresetPassesCore" }; + NormalizeTextCore = new AssetPassNormalizeText { Name = "RobustServerAssetGraphNormalizeTextCore" }; + + PresetPassesCore.AddDependency(InputCore); + NormalizeTextCore.AddDependency(PresetPassesCore).AddBefore(Output); + Output.AddDependency(PresetPassesCore); + Output.AddDependency(NormalizeTextCore); + + // + // Resource files + // + + // Ditto about parallelizing + InputResources = new AssetPassPipe { Name = "RobustServerAssetGraphInputResources", Parallelize = parallel }; + PresetPassesResources = new AssetPassPipe { Name = "RobustServerAssetGraphPresetPassesResources" }; + NormalizeTextResources = new AssetPassNormalizeText { Name = "RobustServerAssetGraphNormalizeTextResources" }; + AudioMetadata = new AssetPassAudioMetadata { Name = "RobustServerAssetGraphAudioMetadata" }; + PrefixResources = new AssetPassPrefix("Resources/") { Name = "RobustServerAssetGraphPrefixResources" }; + + PresetPassesResources.AddDependency(InputResources); + AudioMetadata.AddDependency(PresetPassesResources).AddBefore(NormalizeTextResources); + NormalizeTextResources.AddDependency(PresetPassesResources).AddBefore(PrefixResources); + PrefixResources.AddDependency(PresetPassesResources); + PrefixResources.AddDependency(AudioMetadata); + PrefixResources.AddDependency(NormalizeTextResources); + Output.AddDependency(PrefixResources); + + AllPasses = new AssetPass[] + { + Output, + InputCore, + PresetPassesCore, + NormalizeTextCore, + InputResources, + PresetPassesResources, + NormalizeTextResources, + AudioMetadata, + PrefixResources, + }; + } +} diff --git a/Robust.Packaging/RobustServerPackaging.cs b/Robust.Packaging/RobustServerPackaging.cs index a53bad809..6c06da5ac 100644 --- a/Robust.Packaging/RobustServerPackaging.cs +++ b/Robust.Packaging/RobustServerPackaging.cs @@ -6,7 +6,6 @@ public sealed class RobustServerPackaging { public static IReadOnlySet ServerIgnoresResources { get; } = new HashSet { - "Audio", "Textures", "Fonts", "Shaders", @@ -19,15 +18,16 @@ public sealed class RobustServerPackaging { var ignoreSet = ServerIgnoresResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet(); - await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), + await RobustSharedPackaging.DoResourceCopy( + Path.Combine(contentDir, "Resources"), pass, ignoreSet, - "Resources", - cancel); - await RobustSharedPackaging.DoResourceCopy(Path.Combine("RobustToolbox", "Resources"), + cancel: cancel); + + await RobustSharedPackaging.DoResourceCopy( + Path.Combine("RobustToolbox", "Resources"), pass, ignoreSet, - "Resources", - cancel); + cancel: cancel); } } diff --git a/Robust.Server/Audio/AudioSystem.Effects.cs b/Robust.Server/Audio/AudioSystem.Effects.cs new file mode 100644 index 000000000..ea14d7116 --- /dev/null +++ b/Robust.Server/Audio/AudioSystem.Effects.cs @@ -0,0 +1,79 @@ +using Robust.Shared.Audio; +using Robust.Shared.Audio.Components; +using Robust.Shared.Audio.Effects; +using Robust.Shared.Collections; +using Robust.Shared.GameObjects; + +namespace Robust.Server.Audio; + +public sealed partial class AudioSystem +{ + protected override void InitializeEffect() + { + base.InitializeEffect(); + SubscribeLocalEvent(OnEffectAdd); + SubscribeLocalEvent(OnAuxiliaryAdd); + } + + private void ShutdownEffect() + { + } + + /// + /// Reloads all entities. + /// + public void ReloadPresets() + { + var query = AllEntityQuery(); + var toDelete = new ValueList(); + + while (query.MoveNext(out var uid, out _)) + { + toDelete.Add(uid); + } + + foreach (var ent in toDelete) + { + Del(ent); + } + + foreach (var proto in ProtoMan.EnumeratePrototypes()) + { + if (!proto.CreateAuxiliary) + continue; + + var effect = CreateEffect(); + var aux = CreateAuxiliary(); + SetEffectPreset(effect.Entity, effect.Component, proto); + SetEffect(aux.Entity, aux.Component, effect.Entity); + var preset = AddComp(aux.Entity); + _auxiliaries.Remove(preset.Preset); + preset.Preset = proto.ID; + _auxiliaries[preset.Preset] = aux.Entity; + } + } + + private void OnEffectAdd(EntityUid uid, AudioEffectComponent component, ComponentAdd args) + { + component.Effect = new DummyAudioEffect(); + } + + private void OnAuxiliaryAdd(EntityUid uid, AudioAuxiliaryComponent component, ComponentAdd args) + { + component.Auxiliary = new DummyAuxiliaryAudio(); + } + + public override (EntityUid Entity, AudioAuxiliaryComponent Component) CreateAuxiliary() + { + var (ent, comp) = base.CreateAuxiliary(); + _pvs.AddGlobalOverride(GetNetEntity(ent)); + return (ent, comp); + } + + public override (EntityUid Entity, AudioEffectComponent Component) CreateEffect() + { + var (ent, comp) = base.CreateEffect(); + _pvs.AddGlobalOverride(GetNetEntity(ent)); + return (ent, comp); + } +} diff --git a/Robust.Server/Audio/AudioSystem.cs b/Robust.Server/Audio/AudioSystem.cs new file mode 100644 index 000000000..5a5fd9f7f --- /dev/null +++ b/Robust.Server/Audio/AudioSystem.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using Robust.Server.GameObjects; +using Robust.Server.GameStates; +using Robust.Shared.Audio; +using Robust.Shared.Audio.AudioLoading; +using Robust.Shared.Audio.Components; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Audio.Systems; +using Robust.Shared.ContentPack; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Robust.Server.Audio; + +public sealed partial class AudioSystem : SharedAudioSystem +{ + [Dependency] private readonly PvsOverrideSystem _pvs = default!; + [Dependency] private readonly IResourceManager _resourceManager = default!; + + private readonly Dictionary _cachedAudioLengths = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAudioStartup); + } + + public override void Shutdown() + { + base.Shutdown(); + ShutdownEffect(); + } + + private void OnAudioStartup(EntityUid uid, AudioComponent component, ComponentStartup args) + { + component.Source = new DummyAudioSource(); + } + + private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter) + { + var count = filter.Count; + + if (count == 0) + return; + + var nent = GetNetEntity(uid); + _pvs.AddSessionOverrides(nent, filter); + + var ents = new HashSet(count); + + foreach (var session in filter.Recipients) + { + var ent = session.AttachedEntity; + + if (ent == null) + continue; + + ents.Add(ent.Value); + } + + DebugTools.Assert(component.IncludedEntities == null); + component.IncludedEntities = ents; + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + { + var entity = Spawn("Audio", MapCoordinates.Nullspace); + var audio = SetupAudio(entity, filename, audioParams); + AddAudioFilter(entity, audio, playerFilter); + + return (entity, audio); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) + { + if (!Exists(uid)) + return null; + + var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero)); + var audio = SetupAudio(entity, filename, audioParams); + AddAudioFilter(entity, audio, playerFilter); + + return (entity, audio); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null) + { + if (!Exists(uid)) + return null; + + var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero)); + var audio = SetupAudio(entity, filename, audioParams); + + return (entity, audio); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + { + if (!coordinates.IsValid(EntityManager)) + return null; + + var entity = Spawn("Audio", coordinates); + var audio = SetupAudio(entity, filename, audioParams); + AddAudioFilter(entity, audio, playerFilter); + + return (entity, audio); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates, + AudioParams? audioParams = null) + { + if (!coordinates.IsValid(EntityManager)) + return null; + + var entity = Spawn("Audio", coordinates); + var audio = SetupAudio(entity, filename, audioParams); + + return (entity, audio); + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null) + { + if (sound == null) + return null; + + var audio = PlayPvs(GetSound(sound), source, audioParams ?? sound.Params); + + if (audio == null) + return null; + + audio.Value.Component.ExcludedEntity = user; + return audio; + } + + /// + public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null) + { + if (sound == null) + return null; + + var audio = PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params); + + if (audio == null) + return null; + + audio.Value.Component.ExcludedEntity = user; + return audio; + } + + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null) + { + return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams); + } + + public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null) + { + if (TryComp(recipient, out ActorComponent? actor)) + return PlayGlobal(filename, actor.PlayerSession, audioParams); + + return null; + } + + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + { + return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams); + } + + public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + { + if (TryComp(recipient, out ActorComponent? actor)) + return PlayEntity(filename, actor.PlayerSession, uid, audioParams); + + return null; + } + + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams); + } + + public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + if (TryComp(recipient, out ActorComponent? actor)) + return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams); + + return null; + } + + protected override TimeSpan GetAudioLengthImpl(string filename) + { + // Check shipped metadata from packaging. + if (ProtoMan.TryIndex(filename, out AudioMetadataPrototype? metadata)) + return metadata.Length; + + // Try loading audio files directly. + // This is necessary in development and environments, + // and when working with audio files uploaded dynamically at runtime. + if (_cachedAudioLengths.TryGetValue(filename, out var length)) + return length; + + if (!_resourceManager.TryContentFileRead(filename, out var stream)) + throw new FileNotFoundException($"Unable to find metadata for audio file {filename}"); + + using (stream) + { + var loadedMetadata = AudioLoader.LoadAudioMetadata(stream, filename); + _cachedAudioLengths.Add(filename, loadedMetadata.Length); + return loadedMetadata.Length; + } + } +} diff --git a/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs b/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs deleted file mode 100644 index c917fc1e5..000000000 --- a/Robust.Server/GameObjects/EntitySystems/AudioSystem.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Player; - -namespace Robust.Server.GameObjects; -[UsedImplicitly] -public sealed class AudioSystem : SharedAudioSystem -{ - [Dependency] private readonly TransformSystem _transform = default!; - - - private uint _streamIndex; - - private sealed class AudioSourceServer : IPlayingAudioStream - { - private readonly uint _id; - private readonly AudioSystem _audioSystem; - private readonly IEnumerable? _sessions; - - internal AudioSourceServer(AudioSystem parent, uint identifier, IEnumerable? sessions = null) - { - _audioSystem = parent; - _id = identifier; - _sessions = sessions; - } - public void Stop() - { - _audioSystem.InternalStop(_id, _sessions); - } - } - - private void InternalStop(uint id, IEnumerable? sessions = null) - { - var msg = new StopAudioMessageClient - { - Identifier = id - }; - - if (sessions == null) - RaiseNetworkEvent(msg); - else - { - foreach (var session in sessions) - { - RaiseNetworkEvent(msg, session.ConnectedClient); - } - } - } - - private uint CacheIdentifier() - { - return unchecked(_streamIndex++); - } - - /// - public override IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) - { - var id = CacheIdentifier(); - var msg = new PlayAudioGlobalMessage - { - FileName = filename, - AudioParams = audioParams ?? AudioParams.Default, - Identifier = id - }; - - RaiseNetworkEvent(msg, playerFilter, recordReplay); - - return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray()); - } - - public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) - { - if(!EntityManager.TryGetComponent(uid, out var transform)) - return null; - - var id = CacheIdentifier(); - - var fallbackCoordinates = GetFallbackCoordinates(transform.MapPosition); - - var msg = new PlayAudioEntityMessage - { - FileName = filename, - Coordinates = GetNetCoordinates(transform.Coordinates), - FallbackCoordinates = GetNetCoordinates(fallbackCoordinates), - NetEntity = GetNetEntity(uid), - AudioParams = audioParams ?? AudioParams.Default, - Identifier = id, - }; - - RaiseNetworkEvent(msg, playerFilter, recordReplay); - - return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray()); - } - - /// - public override IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) - { - var id = CacheIdentifier(); - - var fallbackCoordinates = GetFallbackCoordinates(coordinates.ToMap(EntityManager, _transform)); - - var msg = new PlayAudioPositionalMessage - { - FileName = filename, - Coordinates = GetNetCoordinates(coordinates), - FallbackCoordinates = GetNetCoordinates(fallbackCoordinates), - AudioParams = audioParams ?? AudioParams.Default, - Identifier = id - }; - - RaiseNetworkEvent(msg, playerFilter, recordReplay); - - return new AudioSourceServer(this, id, playerFilter.Recipients.ToArray()); - } - - /// - public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null) - { - if (sound == null) - return null; - - var filter = Filter.Pvs(source, entityManager: EntityManager, playerManager: PlayerManager, cfgManager: CfgManager).RemoveWhereAttachedEntity(e => e == user); - return Play(sound, filter, source, true, audioParams); - } - - public override IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, - AudioParams? audioParams = null) - { - if (sound == null) - return null; - - var filter = Filter.Pvs(coordinates, entityMan: EntityManager, playerMan: PlayerManager).RemoveWhereAttachedEntity(e => e == user); - return Play(sound, filter, coordinates, true, audioParams); - } - - public override IPlayingAudioStream? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null) - { - return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams); - } - - public override IPlayingAudioStream? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null) - { - if (TryComp(recipient, out ActorComponent? actor)) - return PlayGlobal(filename, actor.PlayerSession, audioParams); - return null; - } - - public override IPlayingAudioStream? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) - { - return Play(filename, Filter.SinglePlayer(recipient), uid, false, audioParams); - } - - public override IPlayingAudioStream? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) - { - if (TryComp(recipient, out ActorComponent? actor)) - return PlayEntity(filename, actor.PlayerSession, uid, audioParams); - return null; - } - - public override IPlayingAudioStream? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - return Play(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams); - } - - public override IPlayingAudioStream? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - if (TryComp(recipient, out ActorComponent? actor)) - return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams); - return null; - } -} diff --git a/Robust.Server/GameStates/PvsOverrideSystem.cs b/Robust.Server/GameStates/PvsOverrideSystem.cs index bd022a688..ddef2110c 100644 --- a/Robust.Server/GameStates/PvsOverrideSystem.cs +++ b/Robust.Server/GameStates/PvsOverrideSystem.cs @@ -16,30 +16,38 @@ public sealed class PvsOverrideSystem : EntitySystem /// /// Whether or not to supersede existing overrides. /// If true, this will also recursively send any children of the given index. - public void AddGlobalOverride(EntityUid uid, bool removeExistingOverride = true, bool recursive = false) + public void AddGlobalOverride(NetEntity entity, bool removeExistingOverride = true, bool recursive = false) { - _pvs.EntityPVSCollection.AddGlobalOverride(GetNetEntity(uid), removeExistingOverride, recursive); + _pvs.EntityPVSCollection.AddGlobalOverride(entity, removeExistingOverride, recursive); } /// - /// Used to ensure that an entity is always sent to a specific client. By default this overrides any global or pre-existing - /// client-specific overrides. Unlike global overrides, this is always recursive. + /// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing + /// client-specific overrides. /// /// Whether or not to supersede existing overrides. - /// If true, this will also recursively send any children of the given index. - public void AddSessionOverride(EntityUid uid, ICommonSession session, bool removeExistingOverride = true) + public void AddSessionOverride(NetEntity entity, ICommonSession session, bool removeExistingOverride = true) { - _pvs.EntityPVSCollection.AddSessionOverride(GetNetEntity(uid), session, removeExistingOverride); + _pvs.EntityPVSCollection.AddSessionOverride(entity, session, removeExistingOverride); + } + + // 'placeholder' + public void AddSessionOverrides(NetEntity entity, Filter filter, bool removeExistingOverride = true) + { + foreach (var player in filter.Recipients) + { + AddSessionOverride(entity, player, removeExistingOverride); + } } /// /// Removes any global or client-specific overrides. /// - public void ClearOverride(EntityUid uid, TransformComponent? xform = null) + public void ClearOverride(NetEntity entity, TransformComponent? xform = null) { - if (!Resolve(uid, ref xform)) + if (!TryGetEntity(entity, out var uid) || !Resolve(uid.Value, ref xform)) return; - _pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), xform.Coordinates, true); + _pvs.EntityPVSCollection.UpdateIndex(entity, xform.Coordinates, true); } } diff --git a/Robust.Server/Graphics/ClydeHandle.cs b/Robust.Server/Graphics/ClydeHandle.cs new file mode 100644 index 000000000..313867fa3 --- /dev/null +++ b/Robust.Server/Graphics/ClydeHandle.cs @@ -0,0 +1,8 @@ +using Robust.Shared.Graphics; + +namespace Robust.Server.Graphics; + +public struct ClydeHandle : IClydeHandle +{ + public long Value => -1; +} diff --git a/Robust.Shared.Maths/Box2.cs b/Robust.Shared.Maths/Box2.cs index a5124a604..4083bf24e 100644 --- a/Robust.Shared.Maths/Box2.cs +++ b/Robust.Shared.Maths/Box2.cs @@ -87,6 +87,8 @@ namespace Robust.Shared.Maths get => (TopRight - BottomLeft) * 0.5f; } + public static Box2 Empty = new Box2(); + /// /// A 1x1 unit box with the origin centered. /// diff --git a/Robust.Shared/Audio/AudioDebugCommands.cs b/Robust.Shared/Audio/AudioDebugCommands.cs new file mode 100644 index 000000000..d3e999275 --- /dev/null +++ b/Robust.Shared/Audio/AudioDebugCommands.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Audio.Systems; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Robust.Shared.Audio; + +internal sealed class AudioDebugCommands : LocalizedCommands +{ + [Dependency] private readonly IEntitySystemManager _entitySystem = default!; + + public override string Command => "audio_length"; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteError(LocalizationManager.GetString("cmd-invalid-arg-number-error")); + return; + } + + var audioSystem = _entitySystem.GetEntitySystem(); + var length = audioSystem.GetAudioLength(args[0]); + shell.WriteLine(length.ToString()); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHint(LocalizationManager.GetString("cmd-audio_length-arg-file-name")); + } + + return CompletionResult.Empty; + } +} diff --git a/Robust.Shared/Audio/AudioLoading/AudioLoader.cs b/Robust.Shared/Audio/AudioLoading/AudioLoader.cs new file mode 100644 index 000000000..4bf4ae9aa --- /dev/null +++ b/Robust.Shared/Audio/AudioLoading/AudioLoader.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +namespace Robust.Shared.Audio.AudioLoading; + +/// +/// Implements functionality for loading audio files. +/// +/// +/// +internal static class AudioLoader +{ + /// + /// Test if the given file name is something that we can load. + /// + /// + /// This is detected based on file extension. + /// + public static bool IsLoadableAudioFile(ReadOnlySpan filename) + { + var extension = Path.GetExtension(filename); + return extension is ".wav" or ".ogg"; + } + + /// + /// Load metadata about an audio file. Can handle all supported audio file types. + /// + /// Stream containing audio file data to load. + /// File name of the audio file. Used to detect which file type it is. + public static AudioMetadata LoadAudioMetadata(Stream stream, ReadOnlySpan filename) + { + var extension = Path.GetExtension(filename); + if (extension is ".ogg") + { + return AudioLoaderOgg.LoadAudioMetadata(stream); + } + else if (extension is ".wav") + { + return AudioLoaderWav.LoadAudioMetadata(stream); + } + else + { + throw new ArgumentException($"Unknown file type: {extension}"); + } + } +} + +/// +/// Contains basic metadata of an audio file. +/// +/// +internal record AudioMetadata(TimeSpan Length, int ChannelCount, string? Title = null, string? Artist = null); diff --git a/Robust.Shared/Audio/AudioLoading/AudioLoaderOgg.cs b/Robust.Shared/Audio/AudioLoading/AudioLoaderOgg.cs new file mode 100644 index 000000000..13460d672 --- /dev/null +++ b/Robust.Shared/Audio/AudioLoading/AudioLoaderOgg.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using NVorbis; + +namespace Robust.Shared.Audio.AudioLoading; + +/// +/// Implements functionality for loading ogg audio files. +/// +/// +internal static class AudioLoaderOgg +{ + /// + /// Load metadata for an ogg audio file. + /// + /// Audio file stream to load. + public static AudioMetadata LoadAudioMetadata(Stream stream) + { + using var reader = new VorbisReader(stream); + return new AudioMetadata(reader.TotalTime, reader.Channels, reader.Tags.Title, reader.Tags.Artist); + } + + /// + /// Load an ogg file into raw samples and metadata. + /// + /// Audio file stream to load. + public static OggVorbisData LoadAudioData(Stream stream) + { + using var vorbis = new NVorbis.VorbisReader(stream, false); + + var sampleRate = vorbis.SampleRate; + var channels = vorbis.Channels; + var totalSamples = vorbis.TotalSamples; + + var readSamples = 0; + var buffer = new float[totalSamples * channels]; + + while (readSamples < totalSamples) + { + var read = vorbis.ReadSamples(buffer, readSamples * channels, buffer.Length - readSamples); + if (read == 0) + { + break; + } + + readSamples += read; + } + + return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist); + } + + internal readonly struct OggVorbisData + { + public readonly long TotalSamples; + public readonly long SampleRate; + public readonly long Channels; + public readonly ReadOnlyMemory Data; + public readonly string Title; + public readonly string Artist; + + public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory data, string title, string artist) + { + TotalSamples = totalSamples; + SampleRate = sampleRate; + Channels = channels; + Data = data; + Title = title; + Artist = artist; + } + } +} diff --git a/Robust.Shared/Audio/AudioLoading/AudioLoaderWav.cs b/Robust.Shared/Audio/AudioLoading/AudioLoaderWav.cs new file mode 100644 index 000000000..422acef67 --- /dev/null +++ b/Robust.Shared/Audio/AudioLoading/AudioLoaderWav.cs @@ -0,0 +1,152 @@ +using System; +using System.IO; +using Robust.Shared.Utility; + +namespace Robust.Shared.Audio.AudioLoading; + +/// +/// Implements functionality for loading wav audio files. +/// +/// +internal static class AudioLoaderWav +{ + /// + /// Load metadata for a wav audio file. + /// + /// Audio file stream to load. + public static AudioMetadata LoadAudioMetadata(Stream stream) + { + // TODO: Don't load entire WAV file just to extract metadata. + var data = LoadAudioData(stream); + var length = TimeSpan.FromSeconds(data.Data.Length / (double) data.BlockAlign / data.SampleRate); + return new AudioMetadata(length, data.NumChannels); + } + + /// + /// Load a wav file into raw samples and metadata. + /// + /// Audio file stream to load. + public static WavData LoadAudioData(Stream stream) + { + var reader = new BinaryReader(stream, EncodingHelpers.UTF8, true); + + // Read outer most chunks. + Span fourCc = stackalloc byte[4]; + while (true) + { + ReadFourCC(reader, fourCc); + + if (!fourCc.SequenceEqual("RIFF"u8)) + { + SkipChunk(reader); + continue; + } + + return ReadRiffChunk(reader); + } + } + + private static void SkipChunk(BinaryReader reader) + { + var length = reader.ReadUInt32(); + reader.BaseStream.Position += length; + } + + private static void ReadFourCC(BinaryReader reader, Span fourCc) + { + fourCc[0] = reader.ReadByte(); + fourCc[1] = reader.ReadByte(); + fourCc[2] = reader.ReadByte(); + fourCc[3] = reader.ReadByte(); + } + + private static WavData ReadRiffChunk(BinaryReader reader) + { + Span format = stackalloc byte[4]; + reader.ReadUInt32(); + ReadFourCC(reader, format); + if (!format.SequenceEqual("WAVE"u8)) + { + throw new InvalidDataException("File is not a WAVE file."); + } + + ReadFourCC(reader, format); + if (!format.SequenceEqual("fmt "u8)) + { + throw new InvalidDataException("Expected fmt chunk."); + } + + // Read fmt chunk. + + var size = reader.ReadInt32(); + var afterFmtPos = reader.BaseStream.Position + size; + + var audioType = (WavAudioFormatType)reader.ReadInt16(); + var channels = reader.ReadInt16(); + var sampleRate = reader.ReadInt32(); + var byteRate = reader.ReadInt32(); + var blockAlign = reader.ReadInt16(); + var bitsPerSample = reader.ReadInt16(); + + if (audioType != WavAudioFormatType.PCM) + { + throw new NotImplementedException("Unable to support audio types other than PCM."); + } + + DebugTools.Assert(byteRate == sampleRate * channels * bitsPerSample / 8); + + // Fmt is not of guaranteed size, so use the size header to skip to the end. + reader.BaseStream.Position = afterFmtPos; + + while (true) + { + ReadFourCC(reader, format); + if (!format.SequenceEqual("data"u8)) + { + SkipChunk(reader); + continue; + } + + break; + } + + // We are in the data chunk. + size = reader.ReadInt32(); + var data = reader.ReadBytes(size); + + return new WavData(audioType, channels, sampleRate, byteRate, blockAlign, bitsPerSample, data); + } + + /// + /// See http://soundfile.sapp.org/doc/WaveFormat/ for reference. + /// + internal readonly struct WavData + { + public readonly WavAudioFormatType AudioType; + public readonly short NumChannels; + public readonly int SampleRate; + public readonly int ByteRate; + public readonly short BlockAlign; + public readonly short BitsPerSample; + public readonly ReadOnlyMemory Data; + + public WavData(WavAudioFormatType audioType, short numChannels, int sampleRate, int byteRate, + short blockAlign, short bitsPerSample, ReadOnlyMemory data) + { + AudioType = audioType; + NumChannels = numChannels; + SampleRate = sampleRate; + ByteRate = byteRate; + BlockAlign = blockAlign; + BitsPerSample = bitsPerSample; + Data = data; + } + } + + internal enum WavAudioFormatType : short + { + Unknown = 0, + PCM = 1, + // There's a bunch of other types, those are all unsupported. + } +} diff --git a/Robust.Shared/Audio/AudioMetadataPrototype.cs b/Robust.Shared/Audio/AudioMetadataPrototype.cs new file mode 100644 index 000000000..49188c4d8 --- /dev/null +++ b/Robust.Shared/Audio/AudioMetadataPrototype.cs @@ -0,0 +1,21 @@ +using System; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.Audio; + +/// +/// Stores server-side metadata about an audio file. +/// These prototypes get automatically generated when packaging the server, +/// to allow the server to know audio lengths without shipping the large audio files themselves. +/// +[Prototype(ProtoName)] +public sealed class AudioMetadataPrototype : IPrototype +{ + public const string ProtoName = "audioMetadata"; + + [IdDataField] public string ID { get; set; } = string.Empty; + + [DataField] + public TimeSpan Length; +} diff --git a/Robust.Shared/Audio/AudioParams.cs b/Robust.Shared/Audio/AudioParams.cs index 47d9a5413..a8db8910c 100644 --- a/Robust.Shared/Audio/AudioParams.cs +++ b/Robust.Shared/Audio/AudioParams.cs @@ -1,6 +1,7 @@ using Robust.Shared.Serialization; using System; using System.Diagnostics.Contracts; +using Robust.Shared.Audio.Systems; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.GameObjects; @@ -11,10 +12,7 @@ namespace Robust.Shared.Audio { // https://hackage.haskell.org/package/OpenAL-1.7.0.5/docs/Sound-OpenAL-AL-Attenuation.html - /// - /// Default to the overall attenuation. If set project-wide will use InverseDistanceClamped. This is what you typically want for an audio source. - /// - Default = 0, + Invalid = 0, NoAttenuation = 1 << 0, InverseDistance = 1 << 1, InverseDistanceClamped = 1 << 2, @@ -34,54 +32,56 @@ namespace Robust.Shared.Audio /// /// The DistanceModel to use for this specific source. /// - [DataField("attenuation")] - public Attenuation Attenuation { get; set; } = Default.Attenuation; + [DataField] + public Attenuation Attenuation { get; set; } = Attenuation.LinearDistanceClamped; /// /// Base volume to play the audio at, in dB. /// - [DataField("volume")] + [DataField] public float Volume { get; set; } = Default.Volume; /// /// Scale for the audio pitch. /// - [DataField("pitchscale")] - public float PitchScale { get; set; } = Default.PitchScale; + [DataField] + public float Pitch { get; set; } = Default.Pitch; /// /// Audio bus to play on. /// - [DataField("busname")] + [DataField] public string BusName { get; set; } = Default.BusName; /// /// Only applies to positional audio. /// The maximum distance from which the audio is hearable. /// - [DataField("maxdistance")] + [DataField] public float MaxDistance { get; set; } = Default.MaxDistance; /// /// Used for distance attenuation calculations. Set to 0f to make a sound exempt from distance attenuation. /// - [DataField("rolloffFactor")] + [DataField] public float RolloffFactor { get; set; } = Default.RolloffFactor; /// /// Equivalent of the minimum distance to use for an audio source. /// - [DataField("referenceDistance")] + [DataField] public float ReferenceDistance { get; set; } = Default.ReferenceDistance; - [DataField("loop")] public bool Loop { get; set; } = Default.Loop; + [DataField] + public bool Loop { get; set; } = Default.Loop; - [DataField("playoffset")] public float PlayOffsetSeconds { get; set; } = Default.PlayOffsetSeconds; + [DataField] + public float PlayOffsetSeconds { get; set; } = Default.PlayOffsetSeconds; /// /// If not null, this will randomly modify the pitch scale by adding a number drawn from a normal distribution with this deviation. /// - [DataField("variation")] + [DataField] public float? Variation { get; set; } = null; // For the max distance value: it's 2000 in Godot, but I assume that's PIXELS due to the 2D positioning, @@ -96,21 +96,21 @@ namespace Robust.Shared.Audio public AudioParams( float volume, - float pitchScale, + float pitch, string busName, float maxDistance, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) - : this(volume, pitchScale, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation) + : this(volume, pitch, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation) { } - public AudioParams(float volume, float pitchScale, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this() + public AudioParams(float volume, float pitch, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this() { Volume = volume; - PitchScale = pitchScale; + Pitch = pitch; BusName = busName; MaxDistance = maxDistance; RolloffFactor = rolloffFactor; @@ -163,7 +163,7 @@ namespace Robust.Shared.Audio public readonly AudioParams WithPitchScale(float pitch) { var me = this; - me.PitchScale = pitch; + me.Pitch = pitch; return me; } diff --git a/Robust.Shared/Audio/AudioPresetPrototype.cs b/Robust.Shared/Audio/AudioPresetPrototype.cs new file mode 100644 index 000000000..9e5fe916d --- /dev/null +++ b/Robust.Shared/Audio/AudioPresetPrototype.cs @@ -0,0 +1,91 @@ +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.Audio; + +/// +/// Contains audio defaults to set for sounds. +/// This can be used by to apply an audio preset. +/// +[Prototype("audioPreset")] +public sealed class AudioPresetPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + /// + /// Should the engine automatically create an auxiliary audio effect slot for this. + /// + [DataField] + public bool CreateAuxiliary; + + [DataField] + public float Density; + + [DataField] + public float Diffusion; + + [DataField] + public float Gain; + + [DataField("gainHf")] + public float GainHF; + + [DataField("gainLf")] + public float GainLF; + + [DataField] + public float DecayTime; + + [DataField("decayHfRatio")] + public float DecayHFRatio; + + [DataField("decayLfRatio")] + public float DecayLFRatio; + + [DataField] + public float ReflectionsGain; + + [DataField] + public float ReflectionsDelay; + + [DataField] + public Vector3 ReflectionsPan; + + [DataField] + public float LateReverbGain; + + [DataField] + public float LateReverbDelay; + + [DataField] + public Vector3 LateReverbPan; + + [DataField] + public float EchoTime; + + [DataField] + public float EchoDepth; + + [DataField] + public float ModulationTime; + + [DataField] + public float ModulationDepth; + + [DataField("airAbsorptionGainHf")] + public float AirAbsorptionGainHF; + + [DataField("hfReference")] + public float HFReference; + + [DataField("lfReference")] + public float LFReference; + + [DataField] + public float RoomRolloffFactor; + + [DataField("decayHfLimit")] + public int DecayHFLimit; +} diff --git a/Robust.Shared/Audio/Components/AudioAuxiliaryComponent.cs b/Robust.Shared/Audio/Components/AudioAuxiliaryComponent.cs new file mode 100644 index 000000000..a1648d655 --- /dev/null +++ b/Robust.Shared/Audio/Components/AudioAuxiliaryComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Audio.Effects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Robust.Shared.Audio.Components; + +/// +/// Can have Audio passed to it to apply effects or filters. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedAudioSystem))] +public sealed partial class AudioAuxiliaryComponent : Component +{ + /// + /// Audio effect to attach to this auxiliary audio slot. + /// + [DataField, AutoNetworkedField] + public EntityUid? Effect; + + [ViewVariables] + internal IAuxiliaryAudio Auxiliary = new DummyAuxiliaryAudio(); +} diff --git a/Robust.Shared/Audio/Components/AudioComponent.cs b/Robust.Shared/Audio/Components/AudioComponent.cs new file mode 100644 index 000000000..76bb5b28f --- /dev/null +++ b/Robust.Shared/Audio/Components/AudioComponent.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using Robust.Shared.Audio.Effects; +using Robust.Shared.Audio.Sources; +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.ViewVariables; + +namespace Robust.Shared.Audio.Components; + +/// +/// Stores the audio data for an audio entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedAudioSystem))] +public sealed partial class AudioComponent : Component, IAudioSource +{ + #region Filter + + public override bool SessionSpecific => true; + + /// + /// Used for synchronising audio on client that comes into PVS range. + /// + [DataField(customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField] + public TimeSpan AudioStart; + + #region Filters + + // Don't need to network these as client doesn't care. + + /// + /// If this sound was predicted do we exclude it from a specific entity. + /// Useful for predicted audio. + /// + [DataField] + public EntityUid? ExcludedEntity; + + /// + /// If the sound was filtered what entities were included. + /// + [DataField] + public HashSet? IncludedEntities; + + #endregion + + #endregion + + // We can't just start playing on audio creation as we don't have the correct position yet. + // As such we'll wait for FrameUpdate before we start playing to avoid the position being cooked. + public bool Started = false; + + [AutoNetworkedField] + [DataField(required: true)] + public string FileName = string.Empty; + + /// + /// Audio params. Set this if you want to adjust default volume, max distance, etc. + /// + [AutoNetworkedField] + [DataField] + public AudioParams Params = AudioParams.Default; + + /// + /// Audio source that interacts with OpenAL. + /// + [ViewVariables(VVAccess.ReadOnly)] + internal IAudioSource Source = default!; + + /// + /// Auxiliary entity to pass audio to. + /// + [DataField, AutoNetworkedField] + public EntityUid? Auxiliary; + + /* + * Values for IAudioSource stored on the component and sent to IAudioSource as applicable. + * Most of these aren't networked as they double AudioParams data and these just interact with IAudioSource. + */ + + #region Source + + public void Pause() => Source.Pause(); + + /// + public void StartPlaying() => Source.StartPlaying(); + + /// + public void StopPlaying() => Source.StopPlaying(); + + /// + /// + /// + [ViewVariables] + public bool Playing + { + get => Source.Playing; + set => Source.Playing = value; + } + + /// + /// + /// + [ViewVariables] + public bool Looping + { + get => Source.Looping; + set => Source.Looping = value; + } + + /// + /// + /// + [AutoNetworkedField] + public bool Global { get; set; } + + /// + /// + /// + public float Pitch + { + get => Source.Pitch; + set => Source.Pitch = value; + } + + /// + /// + /// + public float MaxDistance + { + get => Source.MaxDistance; + set => Source.MaxDistance = value; + } + + /// + /// + /// + public float RolloffFactor + { + get => Source.RolloffFactor; + set => Source.RolloffFactor = value; + } + + /// + /// + /// + public float ReferenceDistance + { + get => Source.ReferenceDistance; + set => Source.ReferenceDistance = value; + } + + /// + /// + /// + /// + /// Not replicated as audio always tracks the entity's position. + /// + [ViewVariables] + public Vector2 Position + { + get => Source.Position; + set => Source.Position = value; + } + + /// + /// + /// + [ViewVariables] + [Access(Other = AccessPermissions.ReadWriteExecute)] + public float Volume + { + get => Source.Volume; + set => Source.Volume = value; + } + + /// + /// + /// + [ViewVariables] + [Access(Other = AccessPermissions.ReadWriteExecute)] + public float Gain + { + get => Source.Gain; + set => Source.Gain = value; + } + + /// + /// + /// + [ViewVariables] + [Access(Other = AccessPermissions.ReadWriteExecute)] + public float Occlusion + { + get => Source.Occlusion; + set => Source.Occlusion = value; + } + + /// + /// + /// + [ViewVariables] + public float PlaybackPosition + { + get => Source.PlaybackPosition; + set => Source.PlaybackPosition = value; + } + + /// + /// + /// + /// + /// Not replicated. + /// + [ViewVariables] + public Vector2 Velocity + { + get => Source.Velocity; + set => Source.Velocity = value; + } + + void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio) + { + Source.SetAuxiliary(audio); + } + + #endregion + + public void Dispose() + { + Source.Dispose(); + } +} diff --git a/Robust.Shared/Audio/Components/AudioEffectComponent.cs b/Robust.Shared/Audio/Components/AudioEffectComponent.cs new file mode 100644 index 000000000..4ce5bf716 --- /dev/null +++ b/Robust.Shared/Audio/Components/AudioEffectComponent.cs @@ -0,0 +1,204 @@ +using System; +using Robust.Shared.Audio.Effects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Maths; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Robust.Shared.Audio.Components; + +/// +/// Stores OpenAL audio effect data that can be bound to an . +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedAudioSystem)), AutoGenerateComponentState] +public sealed partial class AudioEffectComponent : Component, IAudioEffect +{ + [ViewVariables] + internal IAudioEffect Effect = new DummyAudioEffect(); + + /// + [DataField, AutoNetworkedField] + public float Density + { + get => Effect.Density; + set => Effect.Density = value; + } + + /// + [DataField, AutoNetworkedField] + public float Diffusion + { + get => Effect.Diffusion; + set => Effect.Diffusion = value; + } + + /// + [DataField, AutoNetworkedField] + public float Gain + { + get => Effect.Gain; + set => Effect.Gain = value; + } + + /// + [DataField, AutoNetworkedField] + public float GainHF + { + get => Effect.GainHF; + set => Effect.GainHF = value; + } + + /// + [DataField, AutoNetworkedField] + public float GainLF + { + get => Effect.GainLF; + set => Effect.GainLF = value; + } + + /// + [DataField, AutoNetworkedField] + public float DecayTime + { + get => Effect.DecayTime; + set => Effect.DecayTime = value; + } + + /// + [DataField, AutoNetworkedField] + public float DecayHFRatio + { + get => Effect.DecayHFRatio; + set => Effect.DecayHFRatio = value; + } + + /// + [DataField, AutoNetworkedField] + public float DecayLFRatio + { + get => Effect.DecayLFRatio; + set => Effect.DecayLFRatio = value; + } + + /// + [DataField, AutoNetworkedField] + public float ReflectionsGain + { + get => Effect.ReflectionsGain; + set => Effect.ReflectionsGain = value; + } + + /// + [DataField, AutoNetworkedField] + public float ReflectionsDelay + { + get => Effect.ReflectionsDelay; + set => Effect.ReflectionsDelay = value; + } + + /// + [DataField, AutoNetworkedField] + public Vector3 ReflectionsPan + { + get => Effect.ReflectionsPan; + set => Effect.ReflectionsPan = value; + } + + /// + [DataField, AutoNetworkedField] + public float LateReverbGain + { + get => Effect.LateReverbGain; + set => Effect.LateReverbGain = value; + } + + /// + [DataField, AutoNetworkedField] + public float LateReverbDelay + { + get => Effect.LateReverbDelay; + set => Effect.LateReverbDelay = value; + } + + /// + [DataField, AutoNetworkedField] + public Vector3 LateReverbPan + { + get => Effect.LateReverbPan; + set => Effect.LateReverbPan = value; + } + + /// + [DataField, AutoNetworkedField] + public float EchoTime + { + get => Effect.EchoTime; + set => Effect.EchoTime = value; + } + + /// + [DataField, AutoNetworkedField] + public float EchoDepth + { + get => Effect.EchoDepth; + set => Effect.EchoDepth = value; + } + + /// + [DataField, AutoNetworkedField] + public float ModulationTime + { + get => Effect.ModulationTime; + set => Effect.ModulationTime = value; + } + + /// + [DataField, AutoNetworkedField] + public float ModulationDepth + { + get => Effect.ModulationDepth; + set => Effect.ModulationDepth = value; + } + + /// + [DataField, AutoNetworkedField] + public float AirAbsorptionGainHF + { + get => Effect.AirAbsorptionGainHF; + set => Effect.AirAbsorptionGainHF = value; + } + + /// + [DataField, AutoNetworkedField] + public float HFReference + { + get => Effect.HFReference; + set => Effect.HFReference = value; + } + + /// + [DataField, AutoNetworkedField] + public float LFReference + { + get => Effect.LFReference; + set => Effect.LFReference = value; + } + + /// + [DataField, AutoNetworkedField] + public float RoomRolloffFactor + { + get => Effect.RoomRolloffFactor; + set => Effect.RoomRolloffFactor = value; + } + + /// + [DataField, AutoNetworkedField] + public int DecayHFLimit + { + get => Effect.DecayHFLimit; + set => Effect.DecayHFLimit = value; + } +} diff --git a/Robust.Shared/Audio/Components/AudioPresetComponent.cs b/Robust.Shared/Audio/Components/AudioPresetComponent.cs new file mode 100644 index 000000000..c9723ed6f --- /dev/null +++ b/Robust.Shared/Audio/Components/AudioPresetComponent.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Audio.Systems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; + +namespace Robust.Shared.Audio.Components; + +/// +/// Marks this entity as being spawned for audio presets in case we need to reload. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedAudioSystem))] +public sealed partial class AudioPresetComponent : Component +{ + [AutoNetworkedField] + public string Preset = string.Empty; +} diff --git a/Robust.Shared/Audio/Effects/DummyAudioEffect.cs b/Robust.Shared/Audio/Effects/DummyAudioEffect.cs new file mode 100644 index 000000000..1c90d3c32 --- /dev/null +++ b/Robust.Shared/Audio/Effects/DummyAudioEffect.cs @@ -0,0 +1,81 @@ +using Robust.Shared.Audio.Components; +using Robust.Shared.Maths; + +namespace Robust.Shared.Audio.Effects; + +/// +internal sealed class DummyAudioEffect : IAudioEffect +{ + public void Dispose() + { + } + + /// + public float Density { get; set; } + + /// + public float Diffusion { get; set; } + + /// + public float Gain { get; set; } + + /// + public float GainHF { get; set; } + + /// + public float GainLF { get; set; } + + /// + public float DecayTime { get; set; } + + /// + public float DecayHFRatio { get; set; } + + /// + public float DecayLFRatio { get; set; } + + /// + public float ReflectionsGain { get; set; } + + /// + public float ReflectionsDelay { get; set; } + + /// + public Vector3 ReflectionsPan { get; set; } + + /// + public float LateReverbGain { get; set; } + + /// + public float LateReverbDelay { get; set; } + + /// + public Vector3 LateReverbPan { get; set; } + + /// + public float EchoTime { get; set; } + + /// + public float EchoDepth { get; set; } + + /// + public float ModulationTime { get; set; } + + /// + public float ModulationDepth { get; set; } + + /// + public float AirAbsorptionGainHF { get; set; } + + /// + public float HFReference { get; set; } + + /// + public float LFReference { get; set; } + + /// + public float RoomRolloffFactor { get; set; } + + /// + public int DecayHFLimit { get; set; } +} diff --git a/Robust.Shared/Audio/Effects/DummyAuxiliaryAudio.cs b/Robust.Shared/Audio/Effects/DummyAuxiliaryAudio.cs new file mode 100644 index 000000000..b685288cd --- /dev/null +++ b/Robust.Shared/Audio/Effects/DummyAuxiliaryAudio.cs @@ -0,0 +1,14 @@ +namespace Robust.Shared.Audio.Effects; + +/// +internal sealed class DummyAuxiliaryAudio : IAuxiliaryAudio +{ + public void Dispose() + { + } + + /// + public void SetEffect(IAudioEffect? effect) + { + } +} diff --git a/Robust.Shared/Audio/Effects/IAudioEffect.cs b/Robust.Shared/Audio/Effects/IAudioEffect.cs new file mode 100644 index 000000000..59d8cc948 --- /dev/null +++ b/Robust.Shared/Audio/Effects/IAudioEffect.cs @@ -0,0 +1,122 @@ +using System; +using Robust.Shared.Maths; + +namespace Robust.Shared.Audio.Effects; + +internal interface IAudioEffect +{ + /// + /// Gets the preset value for . + /// + public float Density { get; set; } + + /// + /// Gets the preset value for . + /// + public float Diffusion { get; set; } + + /// + /// Gets the preset value for . + /// + public float Gain { get; set; } + + /// + /// Gets the preset value for . + /// + public float GainHF { get; set; } + + /// + /// Gets the preset value for . + /// + public float GainLF { get; set; } + + /// + /// Gets the preset value for . + /// + public float DecayTime { get; set; } + + /// + /// Gets the preset value for . + /// + public float DecayHFRatio { get; set; } + + /// + /// Gets the preset value for . + /// + public float DecayLFRatio { get; set; } + + /// + /// Gets the preset value for . + /// + public float ReflectionsGain { get; set; } + + /// + /// Gets the preset value for . + /// + public float ReflectionsDelay { get; set; } + + /// + /// Gets the preset value for . + /// + public Vector3 ReflectionsPan { get; set; } + + /// + /// Gets the preset value for . + /// + public float LateReverbGain { get; set; } + + /// + /// Gets the preset value for . + /// + public float LateReverbDelay { get; set; } + + /// + /// Gets the preset value for . + /// + public Vector3 LateReverbPan { get; set; } + + /// + /// Gets the preset value for . + /// + public float EchoTime { get; set; } + + /// + /// Gets the preset value for . + /// + public float EchoDepth { get; set; } + + /// + /// Gets the preset value for . + /// + public float ModulationTime { get; set; } + + /// + /// Gets the preset value for . + /// + public float ModulationDepth { get; set; } + + /// + /// Gets the preset value for . + /// + public float AirAbsorptionGainHF { get; set; } + + /// + /// Gets the preset value for . + /// + public float HFReference { get; set; } + + /// + /// Gets the preset value for . + /// + public float LFReference { get; set; } + + /// + /// Gets the preset value for . + /// + public float RoomRolloffFactor { get; set; } + + /// + /// Gets the preset value for . + /// + public int DecayHFLimit { get; set; } +} diff --git a/Robust.Shared/Audio/Effects/IAuxiliaryAudio.cs b/Robust.Shared/Audio/Effects/IAuxiliaryAudio.cs new file mode 100644 index 000000000..43e72a6ae --- /dev/null +++ b/Robust.Shared/Audio/Effects/IAuxiliaryAudio.cs @@ -0,0 +1,11 @@ +using System; + +namespace Robust.Shared.Audio.Effects; + +internal interface IAuxiliaryAudio : IDisposable +{ + /// + /// Sets the audio effect for this auxiliary audio slot. + /// + void SetEffect(IAudioEffect? effect); +} diff --git a/Robust.Shared/Audio/Effects/ReverbPresets.cs b/Robust.Shared/Audio/Effects/ReverbPresets.cs new file mode 100644 index 000000000..c94eedb1f --- /dev/null +++ b/Robust.Shared/Audio/Effects/ReverbPresets.cs @@ -0,0 +1,3435 @@ +// +// ReverbPresets.cs +// +// Copyright (C) 2019 OpenTK +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + + +using Robust.Shared.Maths; + +namespace Robust.Shared.Audio.Effects; + +/// +/// A set of reverb presets that can be used with the extension. +/// +public static class ReverbPresets +{ + /// + /// A reverb preset (approximating a generic location). + /// + public static readonly ReverbProperties Generic = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.8913f, + 1.0000f, + 1.4900f, + 0.8300f, + 1.0000f, + 0.0500f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a padded cell). + /// + public static readonly ReverbProperties PaddedCell = new ReverbProperties + ( + 0.1715f, + 1.0000f, + 0.3162f, + 0.0010f, + 1.0000f, + 0.1700f, + 0.1000f, + 1.0000f, + 0.2500f, + 0.0010f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2691f, + 0.0020f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a room). + /// + public static readonly ReverbProperties Room = new ReverbProperties + ( + 0.4287f, + 1.0000f, + 0.3162f, + 0.5929f, + 1.0000f, + 0.4000f, + 0.8300f, + 1.0000f, + 0.1503f, + 0.0020f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.0629f, + 0.0030f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a bathroom). + /// + public static readonly ReverbProperties Bathroom = new ReverbProperties + ( + 0.1715f, + 1.0000f, + 0.3162f, + 0.2512f, + 1.0000f, + 1.4900f, + 0.5400f, + 1.0000f, + 0.6531f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 3.2734f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a living room). + /// + public static readonly ReverbProperties LivingRoom = new ReverbProperties + ( + 0.9766f, + 1.0000f, + 0.3162f, + 0.0010f, + 1.0000f, + 0.5000f, + 0.1000f, + 1.0000f, + 0.2051f, + 0.0030f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2805f, + 0.0040f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a stone room). + /// + public static readonly ReverbProperties StoneRoom = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.7079f, + 1.0000f, + 2.3100f, + 0.6400f, + 1.0000f, + 0.4411f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1003f, + 0.0170f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an auditorium). + /// + public static readonly ReverbProperties Auditorium = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.5781f, + 1.0000f, + 4.3200f, + 0.5900f, + 1.0000f, + 0.4032f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7170f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a concert hall). + /// + public static readonly ReverbProperties ConcertHall = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.5623f, + 1.0000f, + 3.9200f, + 0.7000f, + 1.0000f, + 0.2427f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.9977f, + 0.0290f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a cave). + /// + public static readonly ReverbProperties Cave = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 1.0000f, + 1.0000f, + 2.9100f, + 1.3000f, + 1.0000f, + 0.5000f, + 0.0150f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7063f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating an arena). + /// + public static readonly ReverbProperties Arena = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.4477f, + 1.0000f, + 7.2400f, + 0.3300f, + 1.0000f, + 0.2612f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.0186f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hangar). + /// + public static readonly ReverbProperties Hangar = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.3162f, + 1.0000f, + 10.0500f, + 0.2300f, + 1.0000f, + 0.5000f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2560f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a carpeted hallway). + /// + public static readonly ReverbProperties CarpetedHallway = new ReverbProperties + ( + 0.4287f, + 1.0000f, + 0.3162f, + 0.0100f, + 1.0000f, + 0.3000f, + 0.1000f, + 1.0000f, + 0.1215f, + 0.0020f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1531f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hallway). + /// + public static readonly ReverbProperties Hallway = new ReverbProperties + ( + 0.3645f, + 1.0000f, + 0.3162f, + 0.7079f, + 1.0000f, + 1.4900f, + 0.5900f, + 1.0000f, + 0.2458f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.6615f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a stone corridor). + /// + public static readonly ReverbProperties StoneCorridor = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.7612f, + 1.0000f, + 2.7000f, + 0.7900f, + 1.0000f, + 0.2472f, + 0.0130f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.5758f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an alley). + /// + public static readonly ReverbProperties Alley = new ReverbProperties + ( + 1.0000f, + 0.3000f, + 0.3162f, + 0.7328f, + 1.0000f, + 1.4900f, + 0.8600f, + 1.0000f, + 0.2500f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.9954f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1250f, + 0.9500f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a forest). + /// + public static readonly ReverbProperties Forest = new ReverbProperties + ( + 1.0000f, + 0.3000f, + 0.3162f, + 0.0224f, + 1.0000f, + 1.4900f, + 0.5400f, + 1.0000f, + 0.0525f, + 0.1620f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7682f, + 0.0880f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1250f, + 1.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a city). + /// + public static readonly ReverbProperties City = new ReverbProperties + ( + 1.0000f, + 0.5000f, + 0.3162f, + 0.3981f, + 1.0000f, + 1.4900f, + 0.6700f, + 1.0000f, + 0.0730f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1427f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a mountain). + /// + public static readonly ReverbProperties Mountains = new ReverbProperties + ( + 1.0000f, + 0.2700f, + 0.3162f, + 0.0562f, + 1.0000f, + 1.4900f, + 0.2100f, + 1.0000f, + 0.0407f, + 0.3000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1919f, + 0.1000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 1.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a quarry). + /// + public static readonly ReverbProperties Quarry = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.3162f, + 1.0000f, + 1.4900f, + 0.8300f, + 1.0000f, + 0.0000f, + 0.0610f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.7783f, + 0.0250f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1250f, + 0.7000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a plain). + /// + public static readonly ReverbProperties Plain = new ReverbProperties + ( + 1.0000f, + 0.2100f, + 0.3162f, + 0.1000f, + 1.0000f, + 1.4900f, + 0.5000f, + 1.0000f, + 0.0585f, + 0.1790f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1089f, + 0.1000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 1.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a parking lot). + /// + public static readonly ReverbProperties ParkingLot = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 1.0000f, + 1.0000f, + 1.6500f, + 1.5000f, + 1.0000f, + 0.2082f, + 0.0080f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2652f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a sewer pipe). + /// + public static readonly ReverbProperties Sewerpipe = new ReverbProperties + ( + 0.3071f, + 0.8000f, + 0.3162f, + 0.3162f, + 1.0000f, + 2.8100f, + 0.1400f, + 1.0000f, + 1.6387f, + 0.0140f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 3.2471f, + 0.0210f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an underwater location). + /// + public static readonly ReverbProperties Underwater = new ReverbProperties + ( + 0.3645f, + 1.0000f, + 0.3162f, + 0.0100f, + 1.0000f, + 1.4900f, + 0.1000f, + 1.0000f, + 0.5963f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 7.0795f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 1.1800f, + 0.3480f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a drugged state). + /// + public static readonly ReverbProperties Drugged = new ReverbProperties + ( + 0.4287f, + 0.5000f, + 0.3162f, + 1.0000f, + 1.0000f, + 8.3900f, + 1.3900f, + 1.0000f, + 0.8760f, + 0.0020f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 3.1081f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 1.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a dizzy state). + /// + public static readonly ReverbProperties Dizzy = new ReverbProperties + ( + 0.3645f, + 0.6000f, + 0.3162f, + 0.6310f, + 1.0000f, + 17.2300f, + 0.5600f, + 1.0000f, + 0.1392f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.4937f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 1.0000f, + 0.8100f, + 0.3100f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a psychotic state). + /// + public static readonly ReverbProperties Psychotic = new ReverbProperties + ( + 0.0625f, + 0.5000f, + 0.3162f, + 0.8404f, + 1.0000f, + 7.5600f, + 0.9100f, + 1.0000f, + 0.4864f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 2.4378f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 4.0000f, + 1.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /* Castle Presets */ + + /// + /// A reverb preset (approximating a small room in a castle). + /// + public static readonly ReverbProperties CastleSmallRoom = new ReverbProperties + ( + 1.0000f, + 0.8900f, + 0.3162f, + 0.3981f, + 0.1000f, + 1.2200f, + 0.8300f, + 0.3100f, + 0.8913f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.9953f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1380f, + 0.0800f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a short passage in a castle). + /// + public static readonly ReverbProperties CastleShortPassage = new ReverbProperties + ( + 1.0000f, + 0.8900f, + 0.3162f, + 0.3162f, + 0.1000f, + 2.3200f, + 0.8300f, + 0.3100f, + 0.8913f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0230f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1380f, + 0.0800f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a medium room in a castle). + /// + public static readonly ReverbProperties CastleMediumRoom = new ReverbProperties + ( + 1.0000f, + 0.9300f, + 0.3162f, + 0.2818f, + 0.1000f, + 2.0400f, + 0.8300f, + 0.4600f, + 0.6310f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.5849f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1550f, + 0.0300f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a large room in a castle). + /// + public static readonly ReverbProperties CastleLargeRoom = new ReverbProperties + ( + 1.0000f, + 0.8200f, + 0.3162f, + 0.2818f, + 0.1259f, + 2.5300f, + 0.8300f, + 0.5000f, + 0.4467f, + 0.0340f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0160f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1850f, + 0.0700f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a long passage in a castle). + /// + public static readonly ReverbProperties CastleLongPassage = new ReverbProperties + ( + 1.0000f, + 0.8900f, + 0.3162f, + 0.3981f, + 0.1000f, + 3.4200f, + 0.8300f, + 0.3100f, + 0.8913f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0230f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1380f, + 0.0800f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hall in a castle). + /// + public static readonly ReverbProperties CastleHall = new ReverbProperties + ( + 1.0000f, + 0.8100f, + 0.3162f, + 0.2818f, + 0.1778f, + 3.1400f, + 0.7900f, + 0.6200f, + 0.1778f, + 0.0560f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0240f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a cupboard in a castle). + /// + public static readonly ReverbProperties CastleCupboard = new ReverbProperties + ( + 1.0000f, + 0.8900f, + 0.3162f, + 0.2818f, + 0.1000f, + 0.6700f, + 0.8700f, + 0.3100f, + 1.4125f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 3.5481f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1380f, + 0.0800f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a castle courtyard). + /// + public static readonly ReverbProperties CastleCourtyard = new ReverbProperties + ( + 1.0000f, + 0.4200f, + 0.3162f, + 0.4467f, + 0.1995f, + 2.1300f, + 0.6100f, + 0.2300f, + 0.2239f, + 0.1600f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7079f, + 0.0360f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.3700f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating an alcove in a castle). + /// + public static readonly ReverbProperties CastleAlcove = new ReverbProperties + ( + 1.0000f, + 0.8900f, + 0.3162f, + 0.5012f, + 0.1000f, + 1.6400f, + 0.8700f, + 0.3100f, + 1.0000f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0340f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1380f, + 0.0800f, + 0.2500f, + 0.0000f, + 0.9943f, + 5168.6001f, + 139.5000f, + 0.0000f, + 0x1 + ); + + /* Factory Presets */ + + /// + /// A reverb preset (approximating a small room in a factory). + /// + public static readonly ReverbProperties FactorySmallRoom = new ReverbProperties + ( + 0.3645f, + 0.8200f, + 0.3162f, + 0.7943f, + 0.5012f, + 1.7200f, + 0.6500f, + 1.3100f, + 0.7079f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.7783f, + 0.0240f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1190f, + 0.0700f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a short passage in a factory). + /// + public static readonly ReverbProperties FactoryShortPassage = new ReverbProperties + ( + 0.3645f, + 0.6400f, + 0.2512f, + 0.7943f, + 0.5012f, + 2.5300f, + 0.6500f, + 1.3100f, + 1.0000f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0380f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1350f, + 0.2300f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a medium room in a factory). + /// + public static readonly ReverbProperties FactoryMediumRoom = new ReverbProperties + ( + 0.4287f, + 0.8200f, + 0.2512f, + 0.7943f, + 0.5012f, + 2.7600f, + 0.6500f, + 1.3100f, + 0.2818f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0230f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1740f, + 0.0700f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a large room in a factory). + /// + public static readonly ReverbProperties FactoryLargeRoom = new ReverbProperties + ( + 0.4287f, + 0.7500f, + 0.2512f, + 0.7079f, + 0.6310f, + 4.2400f, + 0.5100f, + 1.3100f, + 0.1778f, + 0.0390f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0230f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2310f, + 0.0700f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a long passage in a factory). + /// + public static readonly ReverbProperties FactoryLongPassage = new ReverbProperties + ( + 0.3645f, + 0.6400f, + 0.2512f, + 0.7943f, + 0.5012f, + 4.0600f, + 0.6500f, + 1.3100f, + 1.0000f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0370f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1350f, + 0.2300f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hall in a factory). + /// + public static readonly ReverbProperties FactoryHall = new ReverbProperties + ( + 0.4287f, + 0.7500f, + 0.3162f, + 0.7079f, + 0.6310f, + 7.4300f, + 0.5100f, + 1.3100f, + 0.0631f, + 0.0730f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.8913f, + 0.0270f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0700f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a cupboard in a factory). + /// + public static readonly ReverbProperties FactoryCupboard = new ReverbProperties + ( + 0.3071f, + 0.6300f, + 0.2512f, + 0.7943f, + 0.5012f, + 0.4900f, + 0.6500f, + 1.3100f, + 1.2589f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.9953f, + 0.0320f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1070f, + 0.0700f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a factory courtyard). + /// + public static readonly ReverbProperties FactoryCourtyard = new ReverbProperties + ( + 0.3071f, + 0.5700f, + 0.3162f, + 0.3162f, + 0.6310f, + 2.3200f, + 0.2900f, + 0.5600f, + 0.2239f, + 0.1400f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.3981f, + 0.0390f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.2900f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an alcove in a factory). + /// + public static readonly ReverbProperties FactoryAlcove = new ReverbProperties + ( + 0.3645f, + 0.5900f, + 0.2512f, + 0.7943f, + 0.5012f, + 3.1400f, + 0.6500f, + 1.3100f, + 1.4125f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.0000f, + 0.0380f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1140f, + 0.1000f, + 0.2500f, + 0.0000f, + 0.9943f, + 3762.6001f, + 362.5000f, + 0.0000f, + 0x1 + ); + + /* Ice Palace Presets */ + + /// + /// A reverb preset (approximating a small room in an ice palace). + /// + public static readonly ReverbProperties IcePalaceSmallRoom = new ReverbProperties + ( + 1.0000f, + 0.8400f, + 0.3162f, + 0.5623f, + 0.2818f, + 1.5100f, + 1.5300f, + 0.2700f, + 0.8913f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1640f, + 0.1400f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a short passage in an ice palace). + /// + public static readonly ReverbProperties IcePalaceShortPassage = new ReverbProperties + ( + 1.0000f, + 0.7500f, + 0.3162f, + 0.5623f, + 0.2818f, + 1.7900f, + 1.4600f, + 0.2800f, + 0.5012f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0190f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1770f, + 0.0900f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a medium room in an ice palace). + /// + public static readonly ReverbProperties IcePalaceMediumRoom = new ReverbProperties + ( + 1.0000f, + 0.8700f, + 0.3162f, + 0.5623f, + 0.4467f, + 2.2200f, + 1.5300f, + 0.3200f, + 0.3981f, + 0.0390f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0270f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1860f, + 0.1200f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a large room in an ice palace). + /// + public static readonly ReverbProperties IcePalaceLargeRoom = new ReverbProperties + ( + 1.0000f, + 0.8100f, + 0.3162f, + 0.5623f, + 0.4467f, + 3.1400f, + 1.5300f, + 0.3200f, + 0.2512f, + 0.0390f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.0000f, + 0.0270f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2140f, + 0.1100f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a long passage in an ice palace). + /// + public static readonly ReverbProperties IcePalaceLongPassage = new ReverbProperties + ( + 1.0000f, + 0.7700f, + 0.3162f, + 0.5623f, + 0.3981f, + 3.0100f, + 1.4600f, + 0.2800f, + 0.7943f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0250f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1860f, + 0.0400f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hall in an ice palace). + /// + public static readonly ReverbProperties IcePalaceHall = new ReverbProperties + ( + 1.0000f, + 0.7600f, + 0.3162f, + 0.4467f, + 0.5623f, + 5.4900f, + 1.5300f, + 0.3800f, + 0.1122f, + 0.0540f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.6310f, + 0.0520f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2260f, + 0.1100f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a cupboard in an ice palace). + /// + public static readonly ReverbProperties IcePalaceCupboard = new ReverbProperties + ( + 1.0000f, + 0.8300f, + 0.3162f, + 0.5012f, + 0.2239f, + 0.7600f, + 1.5300f, + 0.2600f, + 1.1220f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.9953f, + 0.0160f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1430f, + 0.0800f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an ice palace courtyard). + /// + public static readonly ReverbProperties IcePalaceCourtyard = new ReverbProperties + ( + 1.0000f, + 0.5900f, + 0.3162f, + 0.2818f, + 0.3162f, + 2.0400f, + 1.2000f, + 0.3800f, + 0.3162f, + 0.1730f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.3162f, + 0.0430f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2350f, + 0.4800f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an alcove in an ice palace). + /// + public static readonly ReverbProperties IcePalaceAlcove = new ReverbProperties + ( + 1.0000f, + 0.8400f, + 0.3162f, + 0.5623f, + 0.2818f, + 2.7600f, + 1.4600f, + 0.2800f, + 1.1220f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.8913f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1610f, + 0.0900f, + 0.2500f, + 0.0000f, + 0.9943f, + 12428.5000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /* Space Station Presets */ + + /// + /// A reverb preset (approximating a small room in a space station). + /// + public static readonly ReverbProperties SpaceStationSmallRoom = new ReverbProperties + ( + 0.2109f, + 0.7000f, + 0.3162f, + 0.7079f, + 0.8913f, + 1.7200f, + 0.8200f, + 0.5500f, + 0.7943f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0130f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1880f, + 0.2600f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a short passage in a space station). + /// + public static readonly ReverbProperties SpaceStationShortPassage = new ReverbProperties + ( + 0.2109f, + 0.8700f, + 0.3162f, + 0.6310f, + 0.8913f, + 3.5700f, + 0.5000f, + 0.5500f, + 1.0000f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0160f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1720f, + 0.2000f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a medium room in a space station). + /// + public static readonly ReverbProperties SpaceStationMediumRoom = new ReverbProperties + ( + 0.2109f, + 0.7500f, + 0.3162f, + 0.6310f, + 0.8913f, + 3.0100f, + 0.5000f, + 0.5500f, + 0.3981f, + 0.0340f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0350f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2090f, + 0.3100f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a large room in a space station). + /// + public static readonly ReverbProperties SpaceStationLargeRoom = new ReverbProperties + ( + 0.3645f, + 0.8100f, + 0.3162f, + 0.6310f, + 0.8913f, + 3.8900f, + 0.3800f, + 0.6100f, + 0.3162f, + 0.0560f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.8913f, + 0.0350f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2330f, + 0.2800f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a long passage in a space station). + /// + public static readonly ReverbProperties SpaceStationLongPassage = new ReverbProperties + ( + 0.4287f, + 0.8200f, + 0.3162f, + 0.6310f, + 0.8913f, + 4.6200f, + 0.6200f, + 0.5500f, + 1.0000f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0310f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.2300f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hall in a space station). + /// + public static readonly ReverbProperties SpaceStationHall = new ReverbProperties + ( + 0.4287f, + 0.8700f, + 0.3162f, + 0.6310f, + 0.8913f, + 7.1100f, + 0.3800f, + 0.6100f, + 0.1778f, + 0.1000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.6310f, + 0.0470f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.2500f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a cupboard in a space station). + /// + public static readonly ReverbProperties SpaceStationCupboard = new ReverbProperties + ( + 0.1715f, + 0.5600f, + 0.3162f, + 0.7079f, + 0.8913f, + 0.7900f, + 0.8100f, + 0.5500f, + 1.4125f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.7783f, + 0.0180f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1810f, + 0.3100f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an alcove in a space station). + /// + public static readonly ReverbProperties SpaceStationAlcove = new ReverbProperties + ( + 0.2109f, + 0.7800f, + 0.3162f, + 0.7079f, + 0.8913f, + 1.1600f, + 0.8100f, + 0.5500f, + 1.4125f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.0000f, + 0.0180f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1920f, + 0.2100f, + 0.2500f, + 0.0000f, + 0.9943f, + 3316.1001f, + 458.2000f, + 0.0000f, + 0x1 + ); + + /* Wooden Galleon Presets */ + + /// + /// A reverb preset (approximating a small room in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonSmallRoom = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.1122f, + 0.3162f, + 0.7900f, + 0.3200f, + 0.8700f, + 1.0000f, + 0.0320f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.8913f, + 0.0290f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a short passage in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonShortPassage = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.1259f, + 0.3162f, + 1.7500f, + 0.5000f, + 0.8700f, + 0.8913f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.6310f, + 0.0240f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a medium room in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonMediumRoom = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.1000f, + 0.2818f, + 1.4700f, + 0.4200f, + 0.8200f, + 0.8913f, + 0.0490f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.8913f, + 0.0290f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a large room in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonLargeRoom = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.0891f, + 0.2818f, + 2.6500f, + 0.3300f, + 0.8200f, + 0.8913f, + 0.0660f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7943f, + 0.0490f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a long passsage in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonLongPassage = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.1000f, + 0.3162f, + 1.9900f, + 0.4000f, + 0.7900f, + 1.0000f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.4467f, + 0.0360f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hall in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonHall = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.0794f, + 0.2818f, + 3.4500f, + 0.3000f, + 0.8200f, + 0.8913f, + 0.0880f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7943f, + 0.0630f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a cupboard in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonCupboard = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.1413f, + 0.3162f, + 0.5600f, + 0.4600f, + 0.9100f, + 1.1220f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0280f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a courtyard on a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonCourtyard = new ReverbProperties + ( + 1.0000f, + 0.6500f, + 0.3162f, + 0.0794f, + 0.3162f, + 1.7900f, + 0.3500f, + 0.7900f, + 0.5623f, + 0.1230f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1000f, + 0.0320f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an alcove in a wooden galleon). + /// + public static readonly ReverbProperties WoodenGalleonAlcove = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.1259f, + 0.3162f, + 1.2200f, + 0.6200f, + 0.9100f, + 1.1220f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7079f, + 0.0240f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4705.0000f, + 99.6000f, + 0.0000f, + 0x1 + ); + + /* Sports Presets */ + + /// + /// A reverb preset (approximating an empty sports stadium). + /// + public static readonly ReverbProperties SportEmptyStadium = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.4467f, + 0.7943f, + 6.2600f, + 0.5100f, + 1.1000f, + 0.0631f, + 0.1830f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.3981f, + 0.0380f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a squash court). + /// + public static readonly ReverbProperties SportSquashCourt = new ReverbProperties + ( + 1.0000f, + 0.7500f, + 0.3162f, + 0.3162f, + 0.7943f, + 2.2200f, + 0.9100f, + 1.1600f, + 0.4467f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7943f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1260f, + 0.1900f, + 0.2500f, + 0.0000f, + 0.9943f, + 7176.8999f, + 211.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a small swimming pool). + /// + public static readonly ReverbProperties SportSmallSwimmingPool = new ReverbProperties + ( + 1.0000f, + 0.7000f, + 0.3162f, + 0.7943f, + 0.8913f, + 2.7600f, + 1.2500f, + 1.1400f, + 0.6310f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7943f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1790f, + 0.1500f, + 0.8950f, + 0.1900f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a large swimming pool). + /// + public static readonly ReverbProperties SportLargeSwimmingPool = new ReverbProperties + ( + 1.0000f, + 0.8200f, + 0.3162f, + 0.7943f, + 1.0000f, + 5.4900f, + 1.3100f, + 1.1400f, + 0.4467f, + 0.0390f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.5012f, + 0.0490f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2220f, + 0.5500f, + 1.1590f, + 0.2100f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a gymnasium). + /// + public static readonly ReverbProperties SportGymnasium = new ReverbProperties + ( + 1.0000f, + 0.8100f, + 0.3162f, + 0.4467f, + 0.8913f, + 3.1400f, + 1.0600f, + 1.3500f, + 0.3981f, + 0.0290f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.5623f, + 0.0450f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1460f, + 0.1400f, + 0.2500f, + 0.0000f, + 0.9943f, + 7176.8999f, + 211.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a full stadium). + /// + public static readonly ReverbProperties SportFullStadium = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.0708f, + 0.7943f, + 5.2500f, + 0.1700f, + 0.8000f, + 0.1000f, + 0.1880f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2818f, + 0.0380f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a tannoy stadium). + /// + public static readonly ReverbProperties SportStadiumTannoy = new ReverbProperties + ( + 1.0000f, + 0.7800f, + 0.3162f, + 0.5623f, + 0.5012f, + 2.5300f, + 0.8800f, + 0.6800f, + 0.2818f, + 0.2300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.5012f, + 0.0630f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.2000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /* Prefab Presets */ + + /// + /// A reverb preset (approximating a workshop). + /// + public static readonly ReverbProperties PrefabWorkshop = new ReverbProperties + ( + 0.4287f, + 1.0000f, + 0.3162f, + 0.1413f, + 0.3981f, + 0.7600f, + 1.0000f, + 1.0000f, + 1.0000f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a school room). + /// + public static readonly ReverbProperties PrefabSchoolRoom = new ReverbProperties + ( + 0.4022f, + 0.6900f, + 0.3162f, + 0.6310f, + 0.5012f, + 0.9800f, + 0.4500f, + 0.1800f, + 1.4125f, + 0.0170f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0150f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.0950f, + 0.1400f, + 0.2500f, + 0.0000f, + 0.9943f, + 7176.8999f, + 211.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a practise room). + /// + public static readonly ReverbProperties PrefabPractiseRoom = new ReverbProperties + ( + 0.4022f, + 0.8700f, + 0.3162f, + 0.3981f, + 0.5012f, + 1.1200f, + 0.5600f, + 0.1800f, + 1.2589f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0110f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.0950f, + 0.1400f, + 0.2500f, + 0.0000f, + 0.9943f, + 7176.8999f, + 211.2000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an outhouse). + /// + public static readonly ReverbProperties PrefabOuthouse = new ReverbProperties + ( + 1.0000f, + 0.8200f, + 0.3162f, + 0.1122f, + 0.1585f, + 1.3800f, + 0.3800f, + 0.3500f, + 0.8913f, + 0.0240f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.6310f, + 0.0440f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1210f, + 0.1700f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 107.5000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a caravan). + /// + public static readonly ReverbProperties PrefabCaravan = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.0891f, + 0.1259f, + 0.4300f, + 1.5000f, + 1.0000f, + 1.0000f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.9953f, + 0.0120f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /* Dome and Pipe Presets */ + + /// + /// A reverb preset (approximating a dome in a tomb). + /// + public static readonly ReverbProperties DomeTomb = new ReverbProperties + ( + 1.0000f, + 0.7900f, + 0.3162f, + 0.3548f, + 0.2239f, + 4.1800f, + 0.2100f, + 0.1000f, + 0.3868f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.6788f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1770f, + 0.1900f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 20.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a small pipe). + /// + public static readonly ReverbProperties PipeSmall = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.3548f, + 0.2239f, + 5.0400f, + 0.1000f, + 0.1000f, + 0.5012f, + 0.0320f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 2.5119f, + 0.0150f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 20.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating the dome in St. Paul's Cathedral, London). + /// + public static readonly ReverbProperties DomeSaintPauls = new ReverbProperties + ( + 1.0000f, + 0.8700f, + 0.3162f, + 0.3548f, + 0.2239f, + 10.4800f, + 0.1900f, + 0.1000f, + 0.1778f, + 0.0900f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0420f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.1200f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 20.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a long, thin pipe). + /// + public static readonly ReverbProperties PipeLongThin = new ReverbProperties + ( + 0.2560f, + 0.9100f, + 0.3162f, + 0.4467f, + 0.2818f, + 9.2100f, + 0.1800f, + 0.1000f, + 0.7079f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7079f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 20.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a large pipe). + /// + public static readonly ReverbProperties PipeLarge = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.3548f, + 0.2239f, + 8.4500f, + 0.1000f, + 0.1000f, + 0.3981f, + 0.0460f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.5849f, + 0.0320f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 20.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a resonant pipe). + /// + public static readonly ReverbProperties PipeResonant = new ReverbProperties + ( + 0.1373f, + 0.9100f, + 0.3162f, + 0.4467f, + 0.2818f, + 6.8100f, + 0.1800f, + 0.1000f, + 0.7079f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.0000f, + 0.0220f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 20.0000f, + 0.0000f, + 0x0 + ); + + /* Outdoors Presets */ + + /// + /// A reverb preset (approximating an outdoors backyard). + /// + public static readonly ReverbProperties OutdoorsBackyard = new ReverbProperties + ( + 1.0000f, + 0.4500f, + 0.3162f, + 0.2512f, + 0.5012f, + 1.1200f, + 0.3400f, + 0.4600f, + 0.4467f, + 0.0690f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.7079f, + 0.0230f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2180f, + 0.3400f, + 0.2500f, + 0.0000f, + 0.9943f, + 4399.1001f, + 242.9000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating rolling plains). + /// + public static readonly ReverbProperties OutdoorsRollingPlains = new ReverbProperties + ( + 1.0000f, + 0.0000f, + 0.3162f, + 0.0112f, + 0.6310f, + 2.1300f, + 0.2100f, + 0.4600f, + 0.1778f, + 0.3000f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.4467f, + 0.0190f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 1.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4399.1001f, + 242.9000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a deep canyon). + /// + public static readonly ReverbProperties OutdoorsDeepCanyon = new ReverbProperties + ( + 1.0000f, + 0.7400f, + 0.3162f, + 0.1778f, + 0.6310f, + 3.8900f, + 0.2100f, + 0.4600f, + 0.3162f, + 0.2230f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.3548f, + 0.0190f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 1.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 4399.1001f, + 242.9000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a creek). + /// + public static readonly ReverbProperties OutdoorsCreek = new ReverbProperties + ( + 1.0000f, + 0.3500f, + 0.3162f, + 0.1778f, + 0.5012f, + 2.1300f, + 0.2100f, + 0.4600f, + 0.3981f, + 0.1150f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.1995f, + 0.0310f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2180f, + 0.3400f, + 0.2500f, + 0.0000f, + 0.9943f, + 4399.1001f, + 242.9000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a valley). + /// + public static readonly ReverbProperties OutdoorsValley = new ReverbProperties + ( + 1.0000f, + 0.2800f, + 0.3162f, + 0.0282f, + 0.1585f, + 2.8800f, + 0.2600f, + 0.3500f, + 0.1413f, + 0.2630f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.3981f, + 0.1000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.3400f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 107.5000f, + 0.0000f, + 0x0 + ); + + /* Mood Presets */ + + /// + /// A reverb preset (approximating a heavenly mood). + /// + public static readonly ReverbProperties MoodHeaven = new ReverbProperties + ( + 1.0000f, + 0.9400f, + 0.3162f, + 0.7943f, + 0.4467f, + 5.0400f, + 1.1200f, + 0.5600f, + 0.2427f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0290f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0800f, + 2.7420f, + 0.0500f, + 0.9977f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a hellish mood). + /// + public static readonly ReverbProperties MoodHell = new ReverbProperties + ( + 1.0000f, + 0.5700f, + 0.3162f, + 0.3548f, + 0.4467f, + 3.5700f, + 0.4900f, + 2.0000f, + 0.0000f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1100f, + 0.0400f, + 2.1090f, + 0.5200f, + 0.9943f, + 5000.0000f, + 139.5000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating an abstract memory). + /// + public static readonly ReverbProperties MoodMemory = new ReverbProperties + ( + 1.0000f, + 0.8500f, + 0.3162f, + 0.6310f, + 0.3548f, + 4.0600f, + 0.8200f, + 0.5600f, + 0.0398f, + 0.0000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.1220f, + 0.0000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.4740f, + 0.4500f, + 0.9886f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /* Driving Presets */ + + /// + /// A reverb preset (approximating a person in the commentator's seat). + /// + public static readonly ReverbProperties DrivingCommentator = new ReverbProperties + ( + 1.0000f, + 0.0000f, + 0.3162f, + 0.5623f, + 0.5012f, + 2.4200f, + 0.8800f, + 0.6800f, + 0.1995f, + 0.0930f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2512f, + 0.0170f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 1.0000f, + 0.2500f, + 0.0000f, + 0.9886f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a pit or garage). + /// + public static readonly ReverbProperties DrivingPitGarage = new ReverbProperties + ( + 0.4287f, + 0.5900f, + 0.3162f, + 0.7079f, + 0.5623f, + 1.7200f, + 0.9300f, + 0.8700f, + 0.5623f, + 0.0000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0160f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.1100f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating driving in a race car). + /// + public static readonly ReverbProperties DrivingInCarRacer = new ReverbProperties + ( + 0.0832f, + 0.8000f, + 0.3162f, + 1.0000f, + 0.7943f, + 0.1700f, + 2.0000f, + 0.4100f, + 1.7783f, + 0.0070f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7079f, + 0.0150f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 10268.2002f, + 251.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating driving in a sports car). + /// + public static readonly ReverbProperties DrivingInCarSports = new ReverbProperties + ( + 0.0832f, + 0.8000f, + 0.3162f, + 0.6310f, + 1.0000f, + 0.1700f, + 0.7500f, + 0.4100f, + 1.0000f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.5623f, + 0.0000f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 10268.2002f, + 251.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating driving in a luxury car). + /// + public static readonly ReverbProperties DrivingInCarLuxury = new ReverbProperties + ( + 0.2560f, + 1.0000f, + 0.3162f, + 0.1000f, + 0.5012f, + 0.1300f, + 0.4100f, + 0.4600f, + 0.7943f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.5849f, + 0.0100f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 10268.2002f, + 251.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating driving on a course with a full grand stand). + /// + public static readonly ReverbProperties DrivingFullGrandStand = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 0.2818f, + 0.6310f, + 3.0100f, + 1.3700f, + 1.2800f, + 0.3548f, + 0.0900f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1778f, + 0.0490f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 10420.2002f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating an empty grand stand). + /// + public static readonly ReverbProperties DrivingEmptyGrandStand = new ReverbProperties + ( + 1.0000f, + 1.0000f, + 0.3162f, + 1.0000f, + 0.7943f, + 4.6200f, + 1.7500f, + 1.4000f, + 0.2082f, + 0.0900f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2512f, + 0.0490f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.0000f, + 0.9943f, + 10420.2002f, + 250.0000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating driving in a tunnel). + /// + public static readonly ReverbProperties DrivingTunnel = new ReverbProperties + ( + 1.0000f, + 0.8100f, + 0.3162f, + 0.3981f, + 0.8913f, + 3.4200f, + 0.9400f, + 1.3100f, + 0.7079f, + 0.0510f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7079f, + 0.0470f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2140f, + 0.0500f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 155.3000f, + 0.0000f, + 0x1 + ); + + /* City Presets */ + + /// + /// A reverb preset (approximating city streets). + /// + public static readonly ReverbProperties CityStreets = new ReverbProperties + ( + 1.0000f, + 0.7800f, + 0.3162f, + 0.7079f, + 0.8913f, + 1.7900f, + 1.1200f, + 0.9100f, + 0.2818f, + 0.0460f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1995f, + 0.0280f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.2000f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a subway). + /// + public static readonly ReverbProperties CitySubway = new ReverbProperties + ( + 1.0000f, + 0.7400f, + 0.3162f, + 0.7079f, + 0.8913f, + 3.0100f, + 1.2300f, + 0.9100f, + 0.7079f, + 0.0460f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0280f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1250f, + 0.2100f, + 0.2500f, + 0.0000f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a museum). + /// + public static readonly ReverbProperties CityMuseum = new ReverbProperties + ( + 1.0000f, + 0.8200f, + 0.3162f, + 0.1778f, + 0.1778f, + 3.2800f, + 1.4000f, + 0.5700f, + 0.2512f, + 0.0390f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.8913f, + 0.0340f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1300f, + 0.1700f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 107.5000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating a library). + /// + public static readonly ReverbProperties CityLibrary = new ReverbProperties + ( + 1.0000f, + 0.8200f, + 0.3162f, + 0.2818f, + 0.0891f, + 2.7600f, + 0.8900f, + 0.4100f, + 0.3548f, + 0.0290f, + new Vector3(0.0000f, 0.0000f, -0.0000f), + 0.8913f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1300f, + 0.1700f, + 0.2500f, + 0.0000f, + 0.9943f, + 2854.3999f, + 107.5000f, + 0.0000f, + 0x0 + ); + + /// + /// A reverb preset (approximating an underpass). + /// + public static readonly ReverbProperties CityUnderpass = new ReverbProperties + ( + 1.0000f, + 0.8200f, + 0.3162f, + 0.4467f, + 0.8913f, + 3.5700f, + 1.1200f, + 0.9100f, + 0.3981f, + 0.0590f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.8913f, + 0.0370f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.1400f, + 0.2500f, + 0.0000f, + 0.9920f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating an abandoned location). + /// + public static readonly ReverbProperties CityAbandoned = new ReverbProperties + ( + 1.0000f, + 0.6900f, + 0.3162f, + 0.7943f, + 0.8913f, + 3.2800f, + 1.1700f, + 0.9100f, + 0.4467f, + 0.0440f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2818f, + 0.0240f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.2000f, + 0.2500f, + 0.0000f, + 0.9966f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /* Misc. Presets */ + + /// + /// A reverb preset (approximating a dusty room). + /// + public static readonly ReverbProperties DustyRoom = new ReverbProperties + ( + 0.3645f, + 0.5600f, + 0.3162f, + 0.7943f, + 0.7079f, + 1.7900f, + 0.3800f, + 0.2100f, + 0.5012f, + 0.0020f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.2589f, + 0.0060f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2020f, + 0.0500f, + 0.2500f, + 0.0000f, + 0.9886f, + 13046.0000f, + 163.3000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a chapel). + /// + public static readonly ReverbProperties Chapel = new ReverbProperties + ( + 1.0000f, + 0.8400f, + 0.3162f, + 0.5623f, + 1.0000f, + 4.6200f, + 0.6400f, + 1.2300f, + 0.4467f, + 0.0320f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.7943f, + 0.0490f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.2500f, + 0.0000f, + 0.2500f, + 0.1100f, + 0.9943f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x1 + ); + + /// + /// A reverb preset (approximating a small, water-filled room). + /// + public static readonly ReverbProperties SmallWaterRoom = new ReverbProperties + ( + 1.0000f, + 0.7000f, + 0.3162f, + 0.4477f, + 1.0000f, + 1.5100f, + 1.2500f, + 1.1400f, + 0.8913f, + 0.0200f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 1.4125f, + 0.0300f, + new Vector3(0.0000f, 0.0000f, 0.0000f), + 0.1790f, + 0.1500f, + 0.8950f, + 0.1900f, + 0.9920f, + 5000.0000f, + 250.0000f, + 0.0000f, + 0x0 + ); +} diff --git a/Robust.Shared/Audio/Effects/ReverbProperties.cs b/Robust.Shared/Audio/Effects/ReverbProperties.cs new file mode 100644 index 000000000..508cf1893 --- /dev/null +++ b/Robust.Shared/Audio/Effects/ReverbProperties.cs @@ -0,0 +1,211 @@ +// +// ReverbProperties.cs +// +// Copyright (C) 2019 OpenTK +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +using Robust.Shared.Maths; + +namespace Robust.Shared.Audio.Effects; + +/// +/// Defines a set of predefined reverb properties. +/// +public record struct ReverbProperties +{ + /// + /// Gets the preset value for . + /// + public float Density; + + /// + /// Gets the preset value for . + /// + public float Diffusion; + + /// + /// Gets the preset value for . + /// + public float Gain; + + /// + /// Gets the preset value for . + /// + public float GainHF; + + /// + /// Gets the preset value for . + /// + public float GainLF; + + /// + /// Gets the preset value for . + /// + public float DecayTime; + + /// + /// Gets the preset value for . + /// + public float DecayHFRatio; + + /// + /// Gets the preset value for . + /// + public float DecayLFRatio; + + /// + /// Gets the preset value for . + /// + public float ReflectionsGain; + + /// + /// Gets the preset value for . + /// + public float ReflectionsDelay; + + /// + /// Gets the preset value for . + /// + public Vector3 ReflectionsPan; + + /// + /// Gets the preset value for . + /// + public float LateReverbGain; + + /// + /// Gets the preset value for . + /// + public float LateReverbDelay; + + /// + /// Gets the preset value for . + /// + public Vector3 LateReverbPan; + + /// + /// Gets the preset value for . + /// + public float EchoTime; + + /// + /// Gets the preset value for . + /// + public float EchoDepth; + + /// + /// Gets the preset value for . + /// + public float ModulationTime; + + /// + /// Gets the preset value for . + /// + public float ModulationDepth; + + /// + /// Gets the preset value for . + /// + public float AirAbsorptionGainHF; + + /// + /// Gets the preset value for . + /// + public float HFReference; + + /// + /// Gets the preset value for . + /// + public float LFReference; + + /// + /// Gets the preset value for . + /// + public float RoomRolloffFactor; + + /// + /// Gets the preset value for . + /// + public int DecayHFLimit; + + /// + /// Initializes a new instance of the struct. + /// + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + /// See . + public ReverbProperties + ( + float density, + float diffusion, + float gain, + float gainHF, + float gainLF, + float decayTime, + float decayHFRatio, + float decayLFRatio, + float reflectionsGain, + float reflectionsDelay, + Vector3 reflectionsPan, + float lateReverbGain, + float lateReverbDelay, + Vector3 lateReverbPan, + float echoTime, + float echoDepth, + float modulationTime, + float modulationDepth, + float airAbsorptionGainHF, + float hfReference, + float lfReference, + float roomRolloffFactor, + int decayHFLimit + ) + { + Density = density; + Diffusion = diffusion; + Gain = gain; + GainHF = gainHF; + GainLF = gainLF; + DecayTime = decayTime; + DecayHFRatio = decayHFRatio; + DecayLFRatio = decayLFRatio; + ReflectionsGain = reflectionsGain; + ReflectionsDelay = reflectionsDelay; + ReflectionsPan = reflectionsPan; + LateReverbGain = lateReverbGain; + LateReverbDelay = lateReverbDelay; + LateReverbPan = lateReverbPan; + EchoTime = echoTime; + EchoDepth = echoDepth; + ModulationTime = modulationTime; + ModulationDepth = modulationDepth; + AirAbsorptionGainHF = airAbsorptionGainHF; + HFReference = hfReference; + LFReference = lfReference; + RoomRolloffFactor = roomRolloffFactor; + DecayHFLimit = decayHFLimit; + } +} diff --git a/Robust.Shared/Audio/IPlayingAudioStream.cs b/Robust.Shared/Audio/IPlayingAudioStream.cs deleted file mode 100644 index 4315a5dcb..000000000 --- a/Robust.Shared/Audio/IPlayingAudioStream.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Robust.Shared.Audio; -public interface IPlayingAudioStream -{ - void Stop(); -} diff --git a/Robust.Shared/Audio/SoundSystem.cs b/Robust.Shared/Audio/SoundSystem.cs deleted file mode 100644 index dcb5dc058..000000000 --- a/Robust.Shared/Audio/SoundSystem.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Player; -using System; - -namespace Robust.Shared.Audio -{ - /// - /// A static proxy class for interfacing with the AudioSystem. - /// - public static class SoundSystem - { - // These functions are obsolete and I CBF adding new arguments to them. - private static bool _recordReplay = false; - - /// - /// Play an audio file globally, without position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The set of players that will hear the sound. - /// Audio parameters to apply when playing the sound. - [Obsolete("Use SharedAudioSystem.PlayGlobal()")] - public static IPlayingAudioStream? Play(string filename, Filter playerFilter, AudioParams? audioParams = null) - { - var entSystMan = IoCManager.Resolve(); - - // Some timers try to play audio after the system has shut down? - entSystMan.TryGetEntitySystem(out SharedAudioSystem? audio); - return audio?.PlayGlobal(filename, playerFilter, _recordReplay, audioParams); - } - - /// - /// Play an audio file following an entity. - /// - /// The resource path to the OGG Vorbis file to play. - /// The set of players that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. - [Obsolete("Use SharedAudioSystem")] - public static IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, - AudioParams? audioParams = null) - { - var entSystMan = IoCManager.Resolve(); - - // Some timers try to play audio after the system has shut down? - entSystMan.TryGetEntitySystem(out SharedAudioSystem? audio); - return audio?.Play(filename, playerFilter, uid, _recordReplay, audioParams); - } - - /// - /// Play an audio file at a static position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The set of players that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - [Obsolete("Use SharedAudioSystem")] - public static IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, - AudioParams? audioParams = null) - { - var entSystMan = IoCManager.Resolve(); - - // Some timers try to play audio after the system has shut down? - entSystMan.TryGetEntitySystem(out SharedAudioSystem? audio); - return audio?.Play(filename, playerFilter, coordinates, _recordReplay, audioParams); - } - } -} diff --git a/Robust.Shared/Audio/Sources/DummyAudioSource.cs b/Robust.Shared/Audio/Sources/DummyAudioSource.cs new file mode 100644 index 000000000..75338b61c --- /dev/null +++ b/Robust.Shared/Audio/Sources/DummyAudioSource.cs @@ -0,0 +1,83 @@ +using System.Numerics; +using Robust.Shared.Audio.Effects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Robust.Shared.Audio.Sources; + +/// +/// Hey look, it's AudioSource's evil twin brother! +/// +[Virtual] +[DataDefinition] +internal partial class DummyAudioSource : IAudioSource +{ + public static DummyAudioSource Instance { get; } = new(); + + public void Dispose() + { + } + + /// + public void Pause() + { + } + + /// + public void StartPlaying() + { + } + + /// + public void StopPlaying() + { + } + + /// + public bool Playing { get; set; } + + /// + [DataField] + public bool Looping { get; set; } + + /// + [DataField] + public bool Global { get; set; } + + /// + public Vector2 Position { get; set; } + + /// + [DataField] + public float Pitch { get; set; } + + /// + public float Volume { get; set; } + + /// + public float Gain { get; set; } + + /// + [DataField] + public float MaxDistance { get; set; } + + /// + [DataField] + public float RolloffFactor { get; set; } + + /// + [DataField] + public float ReferenceDistance { get; set; } + + /// + public float Occlusion { get; set; } + + /// + public float PlaybackPosition { get; set; } + + /// + public Vector2 Velocity { get; set; } + + public void SetAuxiliary(IAuxiliaryAudio? audio) + { + } +} diff --git a/Robust.Client/Graphics/Audio/DummyBufferedAudioSource.cs b/Robust.Shared/Audio/Sources/DummyBufferedAudioSource.cs similarity index 60% rename from Robust.Client/Graphics/Audio/DummyBufferedAudioSource.cs rename to Robust.Shared/Audio/Sources/DummyBufferedAudioSource.cs index 640168b0b..122a261a8 100644 --- a/Robust.Client/Graphics/Audio/DummyBufferedAudioSource.cs +++ b/Robust.Shared/Audio/Sources/DummyBufferedAudioSource.cs @@ -1,24 +1,11 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Robust.Client.Audio; -using Robust.Client.Input; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Timing; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using Color = Robust.Shared.Maths.Color; -namespace Robust.Client.Graphics.Audio +namespace Robust.Shared.Audio.Sources { /// - /// Hey look, it's ClydeAudio.BufferedAudioSource's evil twin brother! + /// Hey look, it's Audio.BufferedAudioSource's evil twin brother! /// - internal sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource + internal sealed class DummyBufferedAudioSource : DummyAudioSource, IBufferedAudioSource { public new static DummyBufferedAudioSource Instance { get; } = new(); public int SampleRate { get; set; } = 0; diff --git a/Robust.Shared/Audio/Sources/IAudioSource.cs b/Robust.Shared/Audio/Sources/IAudioSource.cs new file mode 100644 index 000000000..f81f85053 --- /dev/null +++ b/Robust.Shared/Audio/Sources/IAudioSource.cs @@ -0,0 +1,82 @@ +using System; +using System.Numerics; +using Robust.Shared.Audio.Effects; +using Robust.Shared.GameObjects; + +namespace Robust.Shared.Audio.Sources; + +/// +/// Engine audio source that directly interacts with OpenAL. +/// +/// +/// This just exists so client can interact with OpenAL and server can interact with nothing. +/// +internal interface IAudioSource : IDisposable +{ + void Pause(); + + /// + /// Tries to start playing the audio if not already playing. + /// + void StartPlaying(); + + /// + /// Stops playing a source if it is currently playing. + /// + void StopPlaying(); + + /// + /// Is the audio source currently playing. + /// + bool Playing { get; set; } + + /// + /// Will the audio source loop when finished playing? + /// + bool Looping { get; set; } + + /// + /// Is the audio source relative to the listener or to the world (global or local). + /// + bool Global { get; set; } + + /// + /// Position of the audio relative to listener. + /// + Vector2 Position { get; set; } + + float Pitch { get; set; } + + /// + /// Decibels of the audio. Converted to gain when setting. + /// + float Volume { get; set; } + + /// + /// Direct gain for audio. + /// + float Gain { get; set; } + + float MaxDistance { get; set; } + + float RolloffFactor { get; set; } + + float ReferenceDistance { get; set; } + + float Occlusion { get; set; } + + /// + /// Current playback position. + /// + float PlaybackPosition { get; set; } + + /// + /// Audio source velocity. Used for the doppler effect. + /// + Vector2 Velocity { get; set; } + + /// + /// Set the auxiliary sendfilter for this audio source. + /// + void SetAuxiliary(IAuxiliaryAudio? audio); +} diff --git a/Robust.Shared/Audio/Sources/IBufferedAudioSource.cs b/Robust.Shared/Audio/Sources/IBufferedAudioSource.cs new file mode 100644 index 000000000..41c7fefba --- /dev/null +++ b/Robust.Shared/Audio/Sources/IBufferedAudioSource.cs @@ -0,0 +1,14 @@ +using System; + +namespace Robust.Shared.Audio.Sources; + +internal interface IBufferedAudioSource : IAudioSource +{ + int SampleRate { get; set; } + int GetNumberOfBuffersProcessed(); + void GetBuffersProcessed(Span handles); + void WriteBuffer(int handle, ReadOnlySpan data); + void WriteBuffer(int handle, ReadOnlySpan data); + void QueueBuffers(ReadOnlySpan handles); + void EmptyBuffers(); +} diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.Effects.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.Effects.cs new file mode 100644 index 000000000..9e5eca6df --- /dev/null +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.Effects.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using Robust.Shared.Audio.Components; +using Robust.Shared.Audio.Effects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Robust.Shared.Audio.Systems; + +public abstract partial class SharedAudioSystem +{ + /* + * This is somewhat limiting but also is the easiest way to expose it to content for now. + */ + + /// + /// Pre-calculated auxiliary effect slots for audio presets. + /// + public IReadOnlyDictionary Auxiliaries => _auxiliaries; + + protected readonly Dictionary _auxiliaries = new(); + + protected virtual void InitializeEffect() + { + SubscribeLocalEvent(OnPresetStartup); + SubscribeLocalEvent(OnPresetShutdown); + } + + private void OnPresetStartup(EntityUid uid, AudioPresetComponent component, ComponentStartup args) + { + _auxiliaries[component.Preset] = uid; + } + + private void OnPresetShutdown(EntityUid uid, AudioPresetComponent component, ComponentShutdown args) + { + _auxiliaries.Remove(component.Preset); + } + + /// + /// Creates an auxiliary audio slot that can have an audio source or audio effect applied to it. + /// + public virtual (EntityUid Entity, AudioAuxiliaryComponent Component) CreateAuxiliary() + { + var ent = Spawn(null, MapCoordinates.Nullspace); + var comp = AddComp(ent); + return (ent, comp); + } + + /// + /// Creates an audio effect that can be used with an auxiliary audio slot. + /// + public virtual (EntityUid Entity, AudioEffectComponent Component) CreateEffect() + { + var ent = Spawn(null, MapCoordinates.Nullspace); + var comp = AddComp(ent); + return (ent, comp); + } + + /// + /// Sets the auxiliary effect slot for a specified audio source. + /// + public virtual void SetAuxiliary(EntityUid uid, AudioComponent audio, EntityUid? auxUid) + { + DebugTools.Assert(auxUid == null || HasComp(auxUid)); + audio.Auxiliary = auxUid; + Dirty(uid, audio); + } + + /// + /// Sets the audio effect for a specified auxiliary effect slot. + /// + public virtual void SetEffect(EntityUid auxUid, AudioAuxiliaryComponent aux, EntityUid? effectUid) + { + DebugTools.Assert(effectUid == null || HasComp(effectUid)); + aux.Effect = effectUid; + Dirty(auxUid, aux); + } + + public void SetEffect(EntityUid? audioUid, AudioComponent? component, string effectProto) + { + if (audioUid == null || component == null) + return; + + SetAuxiliary(audioUid.Value, component, _auxiliaries[effectProto]); + } + + /// + /// Applies an audio preset prototype to an audio effect entity. + /// + public void SetEffectPreset(EntityUid effectUid, AudioEffectComponent effectComp, AudioPresetPrototype preset) + { + effectComp.Density = preset.Density; + effectComp.Diffusion = preset.Diffusion; + effectComp.Gain = preset.Gain; + effectComp.GainHF = preset.GainHF; + effectComp.GainLF = preset.GainLF; + effectComp.DecayTime = preset.DecayTime; + effectComp.DecayHFRatio = preset.DecayHFRatio; + effectComp.DecayLFRatio = preset.DecayLFRatio; + effectComp.ReflectionsGain = preset.ReflectionsGain; + effectComp.ReflectionsDelay = preset.ReflectionsDelay; + effectComp.ReflectionsPan = preset.ReflectionsPan; + effectComp.LateReverbGain = preset.LateReverbGain; + effectComp.LateReverbDelay = preset.LateReverbDelay; + effectComp.LateReverbPan = preset.LateReverbPan; + effectComp.EchoTime = preset.EchoTime; + effectComp.EchoDepth = preset.EchoDepth; + effectComp.ModulationTime = preset.ModulationTime; + effectComp.ModulationDepth = preset.ModulationDepth; + effectComp.AirAbsorptionGainHF = preset.AirAbsorptionGainHF; + effectComp.HFReference = preset.HFReference; + effectComp.LFReference = preset.LFReference; + effectComp.RoomRolloffFactor = preset.RoomRolloffFactor; + effectComp.DecayHFLimit = preset.DecayHFLimit; + + Dirty(effectUid, effectComp); + } + + /// + /// Applies an EAX reverb effect preset to an audio effect. + /// + public void SetEffectPreset(EntityUid effectUid, AudioEffectComponent effectComp, ReverbProperties preset) + { + effectComp.Density = preset.Density; + effectComp.Diffusion = preset.Diffusion; + effectComp.Gain = preset.Gain; + effectComp.GainHF = preset.GainHF; + effectComp.GainLF = preset.GainLF; + effectComp.DecayTime = preset.DecayTime; + effectComp.DecayHFRatio = preset.DecayHFRatio; + effectComp.DecayLFRatio = preset.DecayLFRatio; + effectComp.ReflectionsGain = preset.ReflectionsGain; + effectComp.ReflectionsDelay = preset.ReflectionsDelay; + effectComp.ReflectionsPan = preset.ReflectionsPan; + effectComp.LateReverbGain = preset.LateReverbGain; + effectComp.LateReverbDelay = preset.LateReverbDelay; + effectComp.LateReverbPan = preset.LateReverbPan; + effectComp.EchoTime = preset.EchoTime; + effectComp.EchoDepth = preset.EchoDepth; + effectComp.ModulationTime = preset.ModulationTime; + effectComp.ModulationDepth = preset.ModulationDepth; + effectComp.AirAbsorptionGainHF = preset.AirAbsorptionGainHF; + effectComp.HFReference = preset.HFReference; + effectComp.LFReference = preset.LFReference; + effectComp.RoomRolloffFactor = preset.RoomRolloffFactor; + effectComp.DecayHFLimit = preset.DecayHFLimit; + + Dirty(effectUid, effectComp); + } +} diff --git a/Robust.Shared/Audio/Systems/SharedAudioSystem.cs b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs new file mode 100644 index 000000000..08fc7d71e --- /dev/null +++ b/Robust.Shared/Audio/Systems/SharedAudioSystem.cs @@ -0,0 +1,491 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Audio.Components; +using Robust.Shared.Configuration; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Serialization; +using Robust.Shared.Spawners; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Robust.Shared.Audio.Systems; + +/// +/// Handles audio for robust toolbox inside of the sim. +/// +/// +/// Interacts with AudioManager internally. +/// +public abstract partial class SharedAudioSystem : EntitySystem +{ + [Dependency] protected readonly IConfigurationManager CfgManager = default!; + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] protected readonly IPrototypeManager ProtoMan = default!; + [Dependency] protected readonly IRobustRandom RandMan = default!; + + /// + /// Default max range at which the sound can be heard. + /// + public const float DefaultSoundRange = 20; + + /// + /// Used in the PAS to designate the physics collision mask of occluders. + /// + public int OcclusionCollisionMask { get; set; } + + public float ZOffset; + + public override void Initialize() + { + base.Initialize(); + InitializeEffect(); + ZOffset = CfgManager.GetCVar(CVars.AudioZOffset); + CfgManager.OnValueChanged(CVars.AudioZOffset, SetZOffset); + SubscribeLocalEvent(OnAudioGetStateAttempt); + SubscribeLocalEvent(OnAudioUnpaused); + } + + public override void Shutdown() + { + base.Shutdown(); + CfgManager.UnsubValueChanged(CVars.AudioZOffset, SetZOffset); + } + + protected virtual void SetZOffset(float value) + { + var query = AllEntityQuery(); + var oldZOffset = ZOffset; + ZOffset = value; + + while (query.MoveNext(out var uid, out var audio)) + { + // Pythagoras back to normal then adjust. + var maxDistance = MathF.Pow(audio.Params.MaxDistance, 2) - oldZOffset; + var refDistance = MathF.Pow(audio.Params.ReferenceDistance, 2) - oldZOffset; + + audio.Params.MaxDistance = maxDistance; + audio.Params.ReferenceDistance = refDistance; + audio.Params = GetAdjustedParams(audio.Params); + Dirty(uid, audio); + } + } + + protected virtual void OnAudioUnpaused(EntityUid uid, AudioComponent component, ref EntityUnpausedEvent args) + { + component.AudioStart += args.PausedTime; + } + + private void OnAudioGetStateAttempt(EntityUid uid, AudioComponent component, ref ComponentGetStateAttemptEvent args) + { + var playerEnt = args.Player?.AttachedEntity; + + if ((component.ExcludedEntity != null && playerEnt == component.ExcludedEntity) || + (playerEnt != null && component.IncludedEntities != null && !component.IncludedEntities.Contains(playerEnt.Value))) + { + args.Cancelled = true; + } + } + + /// + /// Considers Z-offset for audio and gets the adjusted distance. + /// + /// + /// Really it's just doing pythagoras for you. + /// + public float GetAudioDistance(float length) + { + return MathF.Sqrt(MathF.Pow(length, 2) + MathF.Pow(ZOffset, 2)); + } + + /// + /// Resolves the filepath to a sound file. + /// + public string GetSound(SoundSpecifier specifier) + { + switch (specifier) + { + case SoundPathSpecifier path: + return path.Path == default ? string.Empty : path.Path.ToString(); + + case SoundCollectionSpecifier collection: + { + if (collection.Collection == null) + return string.Empty; + + var soundCollection = ProtoMan.Index(collection.Collection); + return RandMan.Pick(soundCollection.PickFiles).ToString(); + } + } + + return string.Empty; + } + + #region AudioParams + + protected AudioComponent SetupAudio(EntityUid uid, string fileName, AudioParams? audioParams) + { + DebugTools.Assert(!string.IsNullOrEmpty(fileName)); + audioParams ??= AudioParams.Default; + var comp = AddComp(uid); + comp.FileName = fileName; + comp.Params = GetAdjustedParams(audioParams.Value); + comp.AudioStart = Timing.CurTime; + + if (!audioParams.Value.Loop) + { + var length = GetAudioLength(fileName); + + var despawn = AddComp(uid); + // Don't want to clip audio too short due to imprecision. + despawn.Lifetime = (float) length.TotalSeconds + 0.01f; + } + + return comp; + } + + /// + /// Accounts for ZOffset on audio distance. + /// + private AudioParams GetAdjustedParams(AudioParams audioParams) + { + var maxDistance = GetAudioDistance(audioParams.MaxDistance); + var refDistance = GetAudioDistance(audioParams.ReferenceDistance); + + return audioParams + .WithMaxDistance(maxDistance) + .WithReferenceDistance(refDistance); + } + + /// + /// Sets the audio params volume for an entity. + /// + public void SetVolume(EntityUid? entity, float value, Components.AudioComponent? component = null) + { + if (entity == null || !Resolve(entity.Value, ref component)) + return; + + if (component.Params.Volume.Equals(value)) + return; + + component.Params.Volume = value; + Dirty(entity.Value, component); + } + + #endregion + + /// + /// Gets the timespan of the specified audio. + /// + public TimeSpan GetAudioLength(string filename) + { + if (!filename.StartsWith("/")) + throw new ArgumentException("Path must be rooted"); + + return GetAudioLengthImpl(filename); + } + + protected abstract TimeSpan GetAudioLengthImpl(string filename); + + /// + /// Stops the specified audio entity from playing. + /// + /// + /// Returns null so you can inline the call. + /// + public EntityUid? Stop(EntityUid? uid, Components.AudioComponent? component = null) + { + // One morbillion warnings for logging missing. + if (uid == null || !Resolve(uid.Value, ref component, false)) + return null; + + if (!Timing.IsFirstTimePredicted || (_netManager.IsClient && !IsClientSide(uid.Value))) + return null; + + QueueDel(uid); + return null; + } + + /// + /// Play an audio file globally, without position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The set of players that will hear the sound. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); + + /// + /// Play an audio file globally, without position. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The set of players that will hear the sound. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) + { + return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, sound.Params); + } + + /// + /// Play an audio file globally, without position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The player that will hear the sound. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null); + + /// + /// Play an audio file globally, without position. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The player that will hear the sound. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient) + { + return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params); + } + + /// + /// Play an audio file globally, without position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The player that will hear the sound. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null); + + /// + /// Play an audio file globally, without position. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The player that will hear the sound. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null) + { + return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params); + } + + /// + /// Play an audio file following an entity. + /// + /// The resource path to the OGG Vorbis file to play. + /// The set of players that will hear the sound. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); + + /// + /// Play an audio file following an entity. + /// + /// The resource path to the OGG Vorbis file to play. + /// The player that will hear the sound. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); + + /// + /// Play an audio file following an entity. + /// + /// The resource path to the OGG Vorbis file to play. + /// The player that will hear the sound. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); + + /// + /// Play an audio file following an entity. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The set of players that will hear the sound. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) + { + return sound == null ? null : PlayEntity(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params); + } + + /// + /// Play an audio file following an entity. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The player that will hear the sound. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) + { + return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); + } + + /// + /// Play an audio file following an entity. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The player that will hear the sound. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) + { + return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); + } + + /// + /// Play an audio file following an entity for every entity in PVS range. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null) + { + return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params); + } + + /// + /// Play an audio file at the specified EntityCoordinates for every entity in PVS range. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The EntityCoordinates to attach the audio source to. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + return sound == null ? null : PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params); + } + + /// + /// Play an audio file at the specified EntityCoordinates for every entity in PVS range. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The EntityCoordinates to attach the audio source to. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string filename, + EntityCoordinates coordinates, AudioParams? audioParams = null); + + /// + /// Play an audio file following an entity for every entity in PVS range. + /// + /// The resource path to the OGG Vorbis file to play. + /// The UID of the entity "emitting" the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string filename, EntityUid uid, + AudioParams? audioParams = null); + + /// + /// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range, + /// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays + /// this sound as normal + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The UID of the entity "emitting" the audio. + /// The UID of the user that initiated this sound. This is usually some player's controlled entity. + [return: NotNullIfNotNull("sound")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null); + + /// + /// Plays a predicted sound following an EntityCoordinates. The server will send the sound to every player in PVS range, + /// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays + /// this sound as normal + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The entitycoordinates "emitting" the audio + /// The UID of the user that initiated this sound. This is usually some player's controlled entity. + [return: NotNullIfNotNull("sound")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null); + + /// + /// Play an audio file at a static position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The set of players that will hear the sound. + /// The coordinates at which to play the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); + + /// + /// Play an audio file at a static position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The player that will hear the sound. + /// The coordinates at which to play the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); + + /// + /// Play an audio file at a static position. + /// + /// The resource path to the OGG Vorbis file to play. + /// The player that will hear the sound. + /// The coordinates at which to play the audio. + [return: NotNullIfNotNull("filename")] + public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); + + /// + /// Play an audio file at a static position. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The set of players that will hear the sound. + /// The coordinates at which to play the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) + { + return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay); + } + + /// + /// Play an audio file at a static position. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The player that will hear the sound. + /// The coordinates at which to play the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); + } + + /// + /// Play an audio file at a static position. + /// + /// The sound specifier that points the audio file(s) that should be played. + /// The player that will hear the sound. + /// The coordinates at which to play the audio. + [return: NotNullIfNotNull("sound")] + public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) + { + return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); + } + + // These are just here for replays now. + // We don't actually need them in shared, or netserializable, but this makes net serialization + // and replays happy + + // TODO: This is quite bandwidth intensive. + // Sending bus names and file names as strings is expensive and can be optimized. + // Also there's redundant fields in AudioParams in most cases. + [NetSerializable, Serializable] + protected abstract class AudioMessage : EntityEventArgs + { + public string FileName = string.Empty; + public AudioParams AudioParams; + } + + [NetSerializable, Serializable] + protected sealed class PlayAudioGlobalMessage : AudioMessage + { + } + + [NetSerializable, Serializable] + protected sealed class PlayAudioPositionalMessage : AudioMessage + { + public NetCoordinates Coordinates; + } + + [NetSerializable, Serializable] + protected sealed class PlayAudioEntityMessage : AudioMessage + { + public NetEntity NetEntity; + } +} diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index d05eb7958..279a3ecbf 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Threading; using Lidgren.Network; using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Log; @@ -1017,7 +1018,7 @@ namespace Robust.Shared */ public static readonly CVarDef AudioAttenuation = - CVarDef.Create("audio.attenuation", (int) Attenuation.Default, CVar.REPLICATED | CVar.ARCHIVE); + CVarDef.Create("audio.attenuation", (int) Attenuation.LinearDistanceClamped, CVar.REPLICATED | CVar.ARCHIVE); /// /// Audio device to try to output audio to by default. @@ -1037,6 +1038,9 @@ namespace Robust.Shared public static readonly CVarDef AudioRaycastLength = CVarDef.Create("audio.raycast_length", SharedAudioSystem.DefaultSoundRange, CVar.ARCHIVE | CVar.CLIENTONLY); + public static readonly CVarDef AudioZOffset = + CVarDef.Create("audio.z_offset", -5f, CVar.REPLICATED); + /* * PLAYER */ diff --git a/Robust.Shared/ContentPack/DirLoader.cs b/Robust.Shared/ContentPack/DirLoader.cs index 2e5cb5f8d..0776965f5 100644 --- a/Robust.Shared/ContentPack/DirLoader.cs +++ b/Robust.Shared/ContentPack/DirLoader.cs @@ -10,7 +10,6 @@ using Robust.Shared.Utility; namespace Robust.Shared.ContentPack { - [Virtual] internal partial class ResourceManager { /// diff --git a/Robust.Shared/ContentPack/ResourceManager.cs b/Robust.Shared/ContentPack/ResourceManager.cs index 205e45fa5..5d804d1eb 100644 --- a/Robust.Shared/ContentPack/ResourceManager.cs +++ b/Robust.Shared/ContentPack/ResourceManager.cs @@ -15,6 +15,7 @@ namespace Robust.Shared.ContentPack /// /// Virtual file system for all disk resources. /// + [Virtual] internal partial class ResourceManager : IResourceManagerInternal { [Dependency] private readonly IConfigurationManager _config = default!; diff --git a/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs b/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs deleted file mode 100644 index cbc5e56f3..000000000 --- a/Robust.Shared/GameObjects/EntitySystemMessages/AudioMessages.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Map; -using Robust.Shared.Serialization; -using System; - -#nullable disable - -namespace Robust.Shared.GameObjects; -// TODO: This is quite bandwidth intensive. -// Sending bus names and file names as strings is expensive and can be optimized. -// Also there's redundant fields in AudioParams in most cases. -[Serializable, NetSerializable] -public abstract class AudioMessage : EntityEventArgs -{ - public uint Identifier { get; set; } - public string FileName { get; set; } - public AudioParams AudioParams { get; set; } -} - -[Serializable, NetSerializable] -public sealed class StopAudioMessageClient : EntityEventArgs -{ - public uint Identifier {get; set;} -} - -[Serializable, NetSerializable] -public sealed class PlayAudioGlobalMessage : AudioMessage -{ -} - -[Serializable, NetSerializable] -public sealed class PlayAudioPositionalMessage : AudioMessage -{ - public NetCoordinates Coordinates { get; set; } - public NetCoordinates FallbackCoordinates { get; set; } -} - -[Serializable, NetSerializable] -public sealed class PlayAudioEntityMessage : AudioMessage -{ - public NetEntity NetEntity { get; set; } - public NetCoordinates Coordinates { get; set; } - public NetCoordinates FallbackCoordinates { get; set; } -} diff --git a/Robust.Shared/GameObjects/Systems/SharedAudioSystem.cs b/Robust.Shared/GameObjects/Systems/SharedAudioSystem.cs deleted file mode 100644 index 17c1bbdd8..000000000 --- a/Robust.Shared/GameObjects/Systems/SharedAudioSystem.cs +++ /dev/null @@ -1,302 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Configuration; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Robust.Shared.GameObjects; -public abstract class SharedAudioSystem : EntitySystem -{ - [Dependency] protected readonly IConfigurationManager CfgManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IPrototypeManager _protoMan = default!; - [Dependency] protected readonly IRobustRandom RandMan = default!; - [Dependency] protected readonly ISharedPlayerManager PlayerManager = default!; - [Dependency] private readonly SharedMapSystem _map = default!; - - /// - /// Default max range at which the sound can be heard. - /// - public const float DefaultSoundRange = 25; - - /// - /// Used in the PAS to designate the physics collision mask of occluders. - /// - public int OcclusionCollisionMask { get; set; } - - public string GetSound(SoundSpecifier specifier) - { - switch (specifier) - { - case SoundPathSpecifier path: - return path.Path == default ? string.Empty : path.Path.ToString(); - - case SoundCollectionSpecifier collection: - { - if (collection.Collection == null) - return string.Empty; - - var soundCollection = _protoMan.Index(collection.Collection); - return RandMan.Pick(soundCollection.PickFiles).ToString(); - } - } - - return string.Empty; - } - - /// - /// Play an audio file globally, without position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The set of players that will hear the sound. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null); - - - /// - /// Play an audio file globally, without position. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The set of players that will hear the sound. - /// Audio parameters to apply when playing the sound. - public IPlayingAudioStream? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null) - { - return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params); - } - - /// - /// Play an audio file globally, without position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The player that will hear the sound. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null); - - /// - /// Play an audio file globally, without position. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The player that will hear the sound. - /// Audio parameters to apply when playing the sound. - public IPlayingAudioStream? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null) - { - return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); - } - - /// - /// Play an audio file globally, without position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The player that will hear the sound. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null); - - /// - /// Play an audio file globally, without position. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The player that will hear the sound. - /// Audio parameters to apply when playing the sound. - public IPlayingAudioStream? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null) - { - return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params); - } - - // TODO rename to PlayEntity - /// - /// Play an audio file following an entity. - /// - /// The resource path to the OGG Vorbis file to play. - /// The set of players that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null); - - /// - /// Play an audio file following an entity. - /// - /// The resource path to the OGG Vorbis file to play. - /// The player that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null); - - /// - /// Play an audio file following an entity. - /// - /// The resource path to the OGG Vorbis file to play. - /// The player that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null); - - // TODO rename to PlayEntity - /// - /// Play an audio file following an entity. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The set of players that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public IPlayingAudioStream? Play(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null) - { - return sound == null ? null : Play(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params); - } - - /// - /// Play an audio file following an entity. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The player that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public IPlayingAudioStream? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null) - { - return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); - } - - /// - /// Play an audio file following an entity. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The player that will hear the sound. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public IPlayingAudioStream? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null) - { - return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params); - } - - /// - /// Play an audio file following an entity for every entity in PVS range. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public IPlayingAudioStream? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null) - { - return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params); - } - - /// - /// Play an audio file at the specified EntityCoordinates for every entity in PVS range. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The EntityCoordinates to attach the audio source to. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public IPlayingAudioStream? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - return sound == null ? null : Play(GetSound(sound), Filter.Pvs(coordinates, entityMan: EntityManager, playerMan: PlayerManager), coordinates, true, audioParams ?? sound.Params); - } - - /// - /// Play an audio file following an entity for every entity in PVS range. - /// - /// The resource path to the OGG Vorbis file to play. - /// The UID of the entity "emitting" the audio. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public IPlayingAudioStream? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null) - { - return Play(filename, Filter.Pvs(uid, entityManager: EntityManager, playerManager:PlayerManager, cfgManager:CfgManager), uid, true, audioParams); - } - - /// - /// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range, - /// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays - /// this sound as normal - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The UID of the entity "emitting" the audio. - /// The UID of the user that initiated this sound. This is usually some player's controlled entity. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public abstract IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null); - - /// - /// Plays a predicted sound following an EntityCoordinates. The server will send the sound to every player in PVS range, - /// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays - /// this sound as normal - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The entitycoordinates "emitting" the audio - /// The UID of the user that initiated this sound. This is usually some player's controlled entity. - /// Audio parameters to apply when playing the sound. Defaults to using the sound specifier's parameters - public abstract IPlayingAudioStream? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null); - - // TODO rename to play static - /// - /// Play an audio file at a static position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The set of players that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? Play(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null); - - /// - /// Play an audio file at a static position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The player that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); - - /// - /// Play an audio file at a static position. - /// - /// The resource path to the OGG Vorbis file to play. - /// The player that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - public abstract IPlayingAudioStream? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null); - - // TODO rename to play static - /// - /// Play an audio file at a static position. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The set of players that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - public IPlayingAudioStream? Play(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null) - { - return sound == null ? null : Play(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams ?? sound.Params); - } - - /// - /// Play an audio file at a static position. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The player that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - public IPlayingAudioStream? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); - } - - /// - /// Play an audio file at a static position. - /// - /// The sound specifier that points the audio file(s) that should be played. - /// The player that will hear the sound. - /// The coordinates at which to play the audio. - /// Audio parameters to apply when playing the sound. - public IPlayingAudioStream? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null) - { - return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params); - } - - protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates) - { - if (_mapManager.TryFindGridAt(mapCoordinates, out var gridUid, out var mapGrid)) - return new EntityCoordinates(gridUid, _map.WorldToLocal(gridUid, mapGrid, mapCoordinates.Position)); - - if (_mapManager.HasMapEntity(mapCoordinates.MapId)) - return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates.Position); - - return EntityCoordinates.Invalid; - } -} diff --git a/Robust.Shared/Graphics/IClydeHandle.cs b/Robust.Shared/Graphics/IClydeHandle.cs new file mode 100644 index 000000000..bdff0d4c0 --- /dev/null +++ b/Robust.Shared/Graphics/IClydeHandle.cs @@ -0,0 +1,7 @@ +namespace Robust.Shared.Graphics; + +public interface IClydeHandle +{ + long Value { get; } +} + diff --git a/Robust.Shared/Map/MapManager.MapCollection.cs b/Robust.Shared/Map/MapManager.MapCollection.cs index f8bedd642..df59546bb 100644 --- a/Robust.Shared/Map/MapManager.MapCollection.cs +++ b/Robust.Shared/Map/MapManager.MapCollection.cs @@ -242,7 +242,6 @@ internal partial class MapManager } } - var args = new MapEventArgs(actualId); return actualId; } } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 5774d7deb..9a1ee7a59 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -614,11 +614,10 @@ public partial class SharedPhysicsSystem public Transform GetPhysicsTransform(EntityUid uid, TransformComponent? xform = null, EntityQuery? xformQuery = null) { - if (!Resolve(uid, ref xform)) - return new Transform(); + if (!_xformQuery.Resolve(uid, ref xform)) + return Physics.Transform.Empty; - xformQuery ??= _xformQuery; - var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, xformQuery.Value); + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform); return new Transform(worldPos, worldRot); } @@ -631,7 +630,7 @@ public partial class SharedPhysicsSystem if (!Resolve(uid, ref manager, ref body, ref xform)) return new Box2(); - var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, GetEntityQuery()); + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform); var transform = new Transform(worldPos, (float) worldRot.Theta); @@ -653,10 +652,10 @@ public partial class SharedPhysicsSystem { if (!Resolve(uid, ref body, ref xform, ref manager)) { - return new Box2(); + return Box2.Empty; } - var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, GetEntityQuery()); + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform); var transform = new Transform(worldPos, (float) worldRot.Theta); diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs index 649121e71..5fe04db79 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs @@ -477,11 +477,12 @@ namespace Robust.Shared.Physics.Systems } distance = float.MaxValue; - var input = new DistanceInput(); - - input.TransformA = xfA; - input.TransformB = xfB; - input.UseRadii = true; + var input = new DistanceInput + { + TransformA = xfA, + TransformB = xfB, + UseRadii = true + }; // No requirement on collision being enabled so chainshapes will fail foreach (var fixtureA in managerA.Fixtures.Values) @@ -517,12 +518,70 @@ namespace Robust.Shared.Physics.Systems return true; } + /// + /// Gets the nearest points in map terms and the distance between them. + /// If a body is hard it only considers hard fixtures. + /// + public bool TryGetNearest(EntityUid uid, MapCoordinates coordinates, + out Vector2 point, out float distance, + TransformComponent? xformA = null, FixturesComponent? manager = null, PhysicsComponent? body = null) + { + if (!Resolve(uid, ref xformA) || + xformA.MapID != coordinates.MapId) + { + point = Vector2.Zero; + distance = 0f; + return false; + } + + point = Vector2.Zero; + + if (!Resolve(uid, ref manager, ref body) || + manager.FixtureCount == 0) + { + distance = 0f; + return false; + } + + var xfA = GetPhysicsTransform(uid, xformA); + var xfB = new Transform(coordinates.Position, Angle.Zero); + + distance = float.MaxValue; + var input = new DistanceInput(); + + input.TransformA = xfA; + input.TransformB = xfB; + input.UseRadii = true; + var pointShape = new PhysShapeCircle(10 * float.Epsilon, Vector2.Zero); + + // No requirement on collision being enabled so chainshapes will fail + foreach (var fixtureA in manager.Fixtures.Values) + { + if (body.Hard && !fixtureA.Hard) + continue; + + DebugTools.Assert(fixtureA.ProxyCount <= 1); + + input.ProxyA.Set(fixtureA.Shape, 0); + input.ProxyB.Set(pointShape, 0); + DistanceManager.ComputeDistance(out var output, out _, input); + + if (distance < output.Distance) + continue; + + point = output.PointA; + distance = output.Distance; + } + + return true; + } + /// /// Gets the nearest points in map terms and the distance between them. /// If a body is hard it only considers hard fixtures. /// public bool TryGetNearest(EntityUid uidA, EntityUid uidB, - out Vector2 pointA, + out Vector2 point, out Vector2 pointB, out float distance, TransformComponent? xformA = null, TransformComponent? xformB = null, @@ -532,7 +591,7 @@ namespace Robust.Shared.Physics.Systems if (!Resolve(uidA, ref xformA) || !Resolve(uidB, ref xformB) || xformA.MapID != xformB.MapID) { - pointA = Vector2.Zero; + point = Vector2.Zero; pointB = Vector2.Zero; distance = 0f; return false; @@ -541,7 +600,7 @@ namespace Robust.Shared.Physics.Systems var xfA = GetPhysicsTransform(uidA, xformA); var xfB = GetPhysicsTransform(uidB, xformB); - return TryGetNearest(uidA, uidB, out pointA, out pointB, out distance, xfA, xfB, managerA, managerB, bodyA, bodyB); + return TryGetNearest(uidA, uidB, out point, out pointB, out distance, xfA, xfB, managerA, managerB, bodyA, bodyB); } #endregion diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs index 27a294e20..13f3c65c5 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Velocities.cs @@ -43,9 +43,6 @@ public abstract partial class SharedPhysicsSystem EntityQuery? xformQuery = null, EntityQuery? physicsQuery = null) { - if (!Resolve(uid, ref component)) - return Vector2.Zero; - xformQuery ??= EntityManager.GetEntityQuery(); physicsQuery ??= EntityManager.GetEntityQuery(); @@ -53,7 +50,7 @@ public abstract partial class SharedPhysicsSystem var parent = xform.ParentUid; var localPos = xform.LocalPosition; - var velocity = component.LinearVelocity; + var velocity = component?.LinearVelocity ?? Vector2.Zero; Vector2 angularComponent = Vector2.Zero; while (parent != xform.MapUid && parent.IsValid()) diff --git a/Robust.Shared/Physics/Transform.cs b/Robust.Shared/Physics/Transform.cs index ba0cfde4b..8aee88eb1 100644 --- a/Robust.Shared/Physics/Transform.cs +++ b/Robust.Shared/Physics/Transform.cs @@ -32,6 +32,8 @@ namespace Robust.Shared.Physics // TODO: Probably replace this internally with just the Vector2 and radians but I'd need to re-learn trig so yeah.... public struct Transform { + public static readonly Transform Empty = new Transform(); + public Vector2 Position; public Quaternion2D Quaternion2D; diff --git a/Robust.Shared/Player/Filter.cs b/Robust.Shared/Player/Filter.cs index 2348ec9de..4ba9544ce 100644 --- a/Robust.Shared/Player/Filter.cs +++ b/Robust.Shared/Player/Filter.cs @@ -22,6 +22,8 @@ namespace Robust.Shared.Player public bool SendReliable { get; private set; } + public int Count => _recipients.Count; + public IEnumerable Recipients => _recipients; /// diff --git a/Robust.Shared/Robust.Shared.csproj b/Robust.Shared/Robust.Shared.csproj index 6900ddda2..ae3f0a1dd 100644 --- a/Robust.Shared/Robust.Shared.csproj +++ b/Robust.Shared/Robust.Shared.csproj @@ -10,6 +10,7 @@ + diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.cs b/Robust.Shared/Serialization/Manager/SerializationManager.cs index 6556b5827..54410610d 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.cs @@ -18,7 +18,7 @@ namespace Robust.Shared.Serialization.Manager { public sealed partial class SerializationManager : ISerializationManager { - [IoC.Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly IReflectionManager _reflectionManager = default!; public IReflectionManager ReflectionManager => _reflectionManager; diff --git a/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs b/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs index 51430e406..fee8ae922 100644 --- a/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs +++ b/Robust.Shared/Spawners/SharedTimedDespawnSystem.cs @@ -1,3 +1,4 @@ +using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; diff --git a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs index 1336833e7..1238a6ede 100644 --- a/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs +++ b/Robust.UnitTesting/Shared/GameState/ComponentStateTests.cs @@ -8,6 +8,7 @@ using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Network; +using Robust.Shared.Serialization.Manager.Attributes; namespace Robust.UnitTesting.Shared.GameState; @@ -306,6 +307,6 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class UnknownEntityTestComponent : Component { - [AutoNetworkedField] + [DataField, AutoNetworkedField] public EntityUid? Other; }