Sound framework improvements

This commit is contained in:
Anuken
2020-11-12 18:07:26 -05:00
parent d163cacb6a
commit d1d3ef0c67
24 changed files with 162 additions and 355 deletions

View File

@@ -10,11 +10,10 @@ import arc.util.*;
import arc.util.Log.*;
import mindustry.ai.*;
import mindustry.async.*;
import mindustry.audio.*;
import mindustry.core.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.io.*;
@@ -188,7 +187,6 @@ public class Vars implements Loadable{
public static GameState state;
public static EntityCollisions collisions;
public static Waves waves;
public static LoopControl loops;
public static Platform platform = new Platform(){};
public static Mods mods;
public static Schematics schematics;
@@ -255,7 +253,6 @@ public class Vars implements Loadable{
if(mods == null) mods = new Mods();
content = new ContentLoader();
loops = new LoopControl();
waves = new Waves();
collisions = new EntityCollisions();
world = new World();

View File

@@ -1,67 +0,0 @@
package mindustry.audio;
import arc.*;
import arc.audio.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.*;
public class LoopControl{
private ObjectMap<Sound, SoundData> sounds = new ObjectMap<>();
public void play(Sound sound, float volume){
if(Vars.headless) return;
play(sound, Core.camera.position, volume);
}
public void play(Sound sound, Position pos, float volume){
if(Vars.headless) return;
float baseVol = sound.calcFalloff(pos.getX(), pos.getY());
float vol = baseVol * volume;
SoundData data = sounds.get(sound, SoundData::new);
data.volume += vol;
data.volume = Mathf.clamp(data.volume, 0f, 1f);
data.total += baseVol;
data.sum.add(pos.getX() * baseVol, pos.getY() * baseVol);
}
public void update(){
float avol = Core.settings.getInt("ambientvol", 100) / 100f;
sounds.each((sound, data) -> {
data.curVolume = Mathf.lerpDelta(data.curVolume, data.volume * avol, 0.2f);
boolean play = data.curVolume > 0.01f;
float pan = Mathf.zero(data.total, 0.0001f) ? 0f : sound.calcPan(data.sum.x / data.total, data.sum.y / data.total);
if(data.soundID <= 0 || !sound.isPlaying(data.soundID)){
if(play){
data.soundID = sound.loop(data.curVolume, 1f, pan);
}
}else{
if(data.curVolume <= 0.001f){
sound.stop();
data.soundID = -1;
return;
}
sound.set(data.soundID, pan, data.curVolume);
}
data.volume = 0f;
data.total = 0f;
data.sum.setZero();
});
}
private static class SoundData{
float volume;
float total;
Vec2 sum = new Vec2();
int soundID;
float curVolume;
}
}

View File

@@ -2,18 +2,21 @@ package mindustry.audio;
import arc.*;
import arc.audio.*;
import arc.audio.SoloudAudio.*;
import arc.audio.Filters.*;
import arc.files.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
/** Controls playback of multiple music tracks.*/
public class MusicControl{
/** Controls playback of multiple audio tracks.*/
public class SoundControl{
protected static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.6f, musicWaveChance = 0.46f;
/** normal, ambient music, plays at any time */
@@ -29,12 +32,15 @@ public class MusicControl{
protected float fade;
protected boolean silenced;
protected boolean wasPaused;
protected AudioBus uiBus = new AudioBus();
protected boolean wasPaused, wasPlaying;
protected AudioFilter filter = new BiquadFilter(){{
set(0, 500, 1);
}};
public MusicControl(){
protected ObjectMap<Sound, SoundData> sounds = new ObjectMap<>();
public SoundControl(){
Events.on(ClientLoadEvent.class, e -> reload());
//only run music 10 seconds after a wave spawns
@@ -47,6 +53,13 @@ public class MusicControl{
playRandom();
}
}));
setupFilters();
}
protected void setupFilters(){
Core.audio.soundBus.setFilter(0, filter);
Core.audio.soundBus.setFilterParam(0, Filters.paramWet, 0f);
}
protected void reload(){
@@ -55,6 +68,33 @@ public class MusicControl{
ambientMusic = Seq.with(Musics.game1, Musics.game3, Musics.game6, Musics.game8, Musics.game9);
darkMusic = Seq.with(Musics.game2, Musics.game5, Musics.game7, Musics.game4);
bossMusic = Seq.with(Musics.boss1, Musics.boss2, Musics.game2, Musics.game5);
//setup UI bus for all sounds that are in the UI folder
for(var sound : Core.assets.getAll(Sound.class, new Seq<>())){
var file = Fi.get(Core.assets.getAssetFileName(sound));
if(file.parent().name().equals("ui")){
sound.setBus(uiBus);
}
}
}
public void loop(Sound sound, float volume){
if(Vars.headless) return;
loop(sound, Core.camera.position, volume);
}
public void loop(Sound sound, Position pos, float volume){
if(Vars.headless) return;
float baseVol = sound.calcFalloff(pos.getX(), pos.getY());
float vol = baseVol * volume;
SoundData data = sounds.get(sound, SoundData::new);
data.volume += vol;
data.volume = Mathf.clamp(data.volume, 0f, 1f);
data.total += baseVol;
data.sum.add(pos.getX() * baseVol, pos.getY() * baseVol);
}
public void stop(){
@@ -69,10 +109,30 @@ public class MusicControl{
/** Update and play the right music track.*/
public void update(){
boolean paused = state.isGame() && Core.scene.hasDialog();
boolean playing = state.isGame();
//check if current track is finished
if(current != null && !current.isPlaying()){
current = null;
fade = 0f;
}
//fade the lowpass filter in/out
if(paused != wasPaused){
Core.audio.setFilter(0, paused ? filter : null);
wasPaused = paused;
Core.audio.soundBus.fadeFilterParam(0, Filters.paramWet, paused ? 1f : 0f, 0.4f);
}
//play/stop ordinary effects
if(playing != wasPlaying){
wasPlaying = playing;
if(playing){
Core.audio.soundBus.play();
setupFilters();
}else{
Core.audio.soundBus.stop();
}
}
if(state.isMenu()){
@@ -99,6 +159,42 @@ public class MusicControl{
}
}
}
updateLoops();
}
protected void updateLoops(){
//clear loops when in menu
if(!state.isGame()){
sounds.clear();
return;
}
float avol = Core.settings.getInt("ambientvol", 100) / 100f;
sounds.each((sound, data) -> {
data.curVolume = Mathf.lerpDelta(data.curVolume, data.volume * avol, 0.2f);
boolean play = data.curVolume > 0.01f;
float pan = Mathf.zero(data.total, 0.0001f) ? 0f : sound.calcPan(data.sum.x / data.total, data.sum.y / data.total);
if(data.soundID <= 0 || !Core.audio.isPlaying(data.soundID)){
if(play){
data.soundID = sound.loop(data.curVolume, 1f, pan);
Core.audio.protect(data.soundID, true);
}
}else{
if(data.curVolume <= 0.001f){
sound.stop();
data.soundID = -1;
return;
}
Core.audio.set(data.soundID, pan, data.curVolume);
}
data.volume = 0f;
data.total = 0f;
data.sum.setZero();
});
}
/** Plays a random track.*/
@@ -191,12 +287,6 @@ public class MusicControl{
current = music;
current.setVolume(1f);
current.setLooping(false);
current.setCompletionListener(m -> {
if(current == m){
current = null;
fade = 0f;
}
});
current.play();
}
@@ -208,4 +298,13 @@ public class MusicControl{
protected void silence(){
play(null);
}
protected static class SoundData{
float volume;
float total;
Vec2 sum = new Vec2();
int soundID;
float curVolume;
}
}

View File

@@ -1,5 +1,6 @@
package mindustry.audio;
import arc.*;
import arc.audio.*;
import arc.math.*;
import arc.util.*;
@@ -31,19 +32,19 @@ public class SoundLoop{
}else{
volume = Mathf.clamp(volume - fadeSpeed * Time.delta);
if(volume <= 0.001f){
sound.stop(id);
Core.audio.stop(id);
id = -1;
return;
}
}
sound.set(id, sound.calcPan(x, y), sound.calcVolume(x, y) * volume * baseVolume);
Core.audio.set(id, sound.calcPan(x, y), sound.calcVolume(x, y) * volume * baseVolume);
}
}
public void stop(){
if(id != -1){
sound.stop(id);
Core.audio.stop(id);
id = -1;
volume = baseVolume = -1;
}

View File

@@ -41,7 +41,7 @@ import static mindustry.Vars.*;
*/
public class Control implements ApplicationListener, Loadable{
public Saves saves;
public MusicControl music;
public SoundControl sound;
public Tutorial tutorial;
public InputHandler input;
@@ -52,14 +52,11 @@ public class Control implements ApplicationListener, Loadable{
public Control(){
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
sound = new SoundControl();
Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, platform::updateRPC);
for(Sound sound : assets.getAll(Sound.class, new Seq<>())){
sound.stop();
}
}
});
@@ -497,8 +494,7 @@ public class Control implements ApplicationListener, Loadable{
input.updateState();
music.update();
loops.update();
sound.update();
if(Core.input.keyTap(Binding.fullscreen)){
boolean full = settings.getBool("fullscreen");

View File

@@ -1368,12 +1368,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
if(sound != null){
sound.update(x, y, shouldActiveSound());
}
if(!headless){
if(sound != null){
sound.update(x, y, shouldActiveSound());
}
if(block.ambientSound != Sounds.none && shouldAmbientSound()){
loops.play(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
if(block.ambientSound != Sounds.none && shouldAmbientSound()){
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
}
}
if(enabled || !block.noUpdateDisabled){

View File

@@ -34,7 +34,9 @@ abstract class FireComp implements Timedc, Posc, Firec, Syncc{
Fx.fireSmoke.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
loops.play(Sounds.fire, this, 0.07f);
if(!headless){
control.sound.loop(Sounds.fire, this, 0.07f);
}
time = Mathf.clamp(time + Time.delta, 0, lifetime());

View File

@@ -89,7 +89,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
}
if(!headless){
loops.play(type.mineSound, this, type.mineSoundVolume);
control.sound.loop(type.mineSound, this, type.mineSoundVolume);
}
}
}

View File

@@ -100,7 +100,7 @@ public class LAssembler{
int index = 0;
for(String line : lines){
//comments
if(line.startsWith("#")) continue;
if(line.startsWith("#") || line.isEmpty()) continue;
//remove trailing semicolons in case someone adds them in for no reason
if(line.endsWith(";")) line = line.substring(0, line.length() - 1);

View File

@@ -2,6 +2,8 @@ package mindustry.mod;
import arc.*;
import arc.assets.*;
import arc.assets.loaders.*;
import arc.assets.loaders.SoundLoader.*;
import arc.audio.*;
import arc.files.*;
import arc.func.*;
@@ -89,15 +91,14 @@ public class ContentParser{
});
put(Sound.class, (type, data) -> {
if(fieldOpt(Sounds.class, data) != null) return fieldOpt(Sounds.class, data);
if(Vars.headless) return new MockSound();
if(Vars.headless) return new Sound();
String name = "sounds/" + data.asString();
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
if(Core.assets.contains(path, Sound.class)) return Core.assets.get(path, Sound.class);
ModLoadingSound sound = new ModLoadingSound();
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class);
desc.loaded = result -> sound.sound = (Sound)result;
var sound = new Sound();
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class, new SoundParameter(sound));
desc.errored = Throwable::printStackTrace;
return sound;
});

View File

@@ -1,84 +0,0 @@
package mindustry.mod;
import arc.audio.*;
import arc.mock.*;
import arc.util.*;
public class ModLoadingMusic implements Music{
public Music music = new MockMusic();
@Override
public void play(){
music.play();
}
@Override
public void pause(){
music.pause();
}
@Override
public void stop(){
music.stop();
}
@Override
public boolean isPlaying(){
return music.isPlaying();
}
@Override
public boolean isLooping(){
return music.isLooping();
}
@Override
public void setLooping(boolean isLooping){
music.setLooping(isLooping);
}
@Override
public float getVolume(){
return music.getVolume();
}
@Override
public void setVolume(float volume){
music.setVolume(volume);
}
@Override
public void setPan(float pan, float volume){
music.setPan(pan, volume);
}
@Override
public float getPosition(){
return music.getPosition();
}
@Override
public void setPosition(float position){
music.setPosition(position);
}
@Override
public void dispose(){
music.dispose();
}
@Override
public void setCompletionListener(OnCompletionListener listener){
music.setCompletionListener(listener);
}
@Override
public void setFilter(int index, @Nullable AudioFilter filter){
music.setFilter(index, filter);
}
@Override
public boolean isDisposed(){
return music.isDisposed();
}
}

View File

@@ -1,140 +0,0 @@
package mindustry.mod;
import arc.audio.*;
import arc.math.geom.*;
import arc.mock.*;
import arc.util.*;
public class ModLoadingSound implements Sound{
public Sound sound = new MockSound();
@Override
public float calcPan(float x, float y){
return sound.calcPan(x, y);
}
@Override
public float calcVolume(float x, float y){
return sound.calcVolume(x, y);
}
@Override
public float calcFalloff(float x, float y){
return sound.calcFalloff(x, y);
}
@Override
public int at(float x, float y, float pitch){
return sound.at(x, y, pitch);
}
@Override
public int at(float x, float y){
return sound.at(x, y);
}
@Override
public int at(Position pos){
return sound.at(pos);
}
@Override
public int at(Position pos, float pitch){
return sound.at(pos, pitch);
}
@Override
public int play(){
return sound.play();
}
@Override
public int play(float volume){
return sound.play(volume);
}
@Override
public int play(float volume, float pitch, float pan){
return sound.play(volume, pitch, pan);
}
@Override
public int loop(){
return sound.loop();
}
@Override
public int loop(float volume){
return sound.loop(volume);
}
@Override
public int loop(float volume, float pitch, float pan){
return sound.loop(volume, pitch, pan);
}
@Override
public void stop(){
sound.stop();
}
@Override
public void dispose(){
sound.dispose();
}
@Override
public void stop(int soundId){
sound.stop(soundId);
}
@Override
public void pause(int soundId){
sound.pause(soundId);
}
@Override
public void resume(int soundId){
sound.resume(soundId);
}
@Override
public void setLooping(int soundId, boolean looping){
sound.setLooping(soundId, looping);
}
@Override
public void setPitch(int soundId, float pitch){
sound.setPitch(soundId, pitch);
}
@Override
public void setVolume(int soundId, float volume){
sound.setVolume(soundId, volume);
}
@Override
public void set(int soundId, float pan, float volume){
sound.set(soundId, pan, volume);
}
@Override
public boolean isDisposed(){
return sound.isDisposed();
}
@Override
public int play(float volume, float pitch, float pan, boolean loop){
return sound.play(volume, pitch, pan, loop);
}
@Override
public void setFilter(int index, @Nullable AudioFilter filter){
sound.setFilter(index, filter);
}
@Override
public boolean isPlaying(int soundId){
return sound.isPlaying(soundId);
}
}

View File

@@ -2,9 +2,10 @@ package mindustry.mod;
import arc.*;
import arc.assets.*;
import arc.assets.loaders.MusicLoader.*;
import arc.assets.loaders.SoundLoader.*;
import arc.audio.*;
import arc.files.*;
import arc.mock.*;
import arc.struct.*;
import arc.util.*;
import arc.util.Log.*;
@@ -87,33 +88,31 @@ public class Scripts implements Disposable{
}
public Sound loadSound(String soundName){
if(Vars.headless) return new MockSound();
if(Vars.headless) return new Sound();
String name = "sounds/" + soundName;
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
if(Core.assets.contains(path, Sound.class)) return Core.assets.get(path, Sound.class);
ModLoadingSound sound = new ModLoadingSound();
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class);
desc.loaded = result -> sound.sound = (Sound)result;
var sound = new Sound();
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class, new SoundParameter(sound));
desc.errored = Throwable::printStackTrace;
return sound;
}
public Music loadMusic(String soundName){
if(Vars.headless) return new MockMusic();
if(Vars.headless) return new Music();
String name = "music/" + soundName;
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
if(Core.assets.contains(path, Music.class)) return Core.assets.get(path, Music.class);
ModLoadingMusic sound = new ModLoadingMusic();
AssetDescriptor<?> desc = Core.assets.load(path, Music.class);
desc.loaded = result -> sound.music = (Music)result;
var music = new Music();
AssetDescriptor<?> desc = Core.assets.load(path, Music.class, new MusicParameter(music));
desc.errored = Throwable::printStackTrace;
return sound;
return music;
}
//endregion

View File

@@ -356,12 +356,12 @@ public class UnitType extends UnlockableContent{
ItemStack[] stacks = null;
//calculate costs based on reconstructors or factories found
Block rec = content.blocks().find(b -> b instanceof Reconstructor && ((Reconstructor)b).upgrades.contains(u -> u[1] == this));
Block rec = content.blocks().find(b -> b instanceof Reconstructor re && re.upgrades.contains(u -> u[1] == this));
if(rec != null && rec.consumes.has(ConsumeType.item) && rec.consumes.get(ConsumeType.item) instanceof ConsumeItems){
stacks = ((ConsumeItems)rec.consumes.get(ConsumeType.item)).items;
if(rec != null && rec.consumes.has(ConsumeType.item) && rec.consumes.get(ConsumeType.item) instanceof ConsumeItems ci){
stacks = ci.items;
}else{
UnitFactory factory = (UnitFactory)content.blocks().find(u -> u instanceof UnitFactory && ((UnitFactory)u).plans.contains(p -> p.unit == this));
UnitFactory factory = (UnitFactory)content.blocks().find(u -> u instanceof UnitFactory uf && uf.plans.contains(p -> p.unit == this));
if(factory != null){
stacks = factory.plans.find(p -> p.unit == this).requirements;
}
@@ -370,7 +370,7 @@ public class UnitType extends UnlockableContent{
if(stacks != null){
ItemStack[] out = new ItemStack[stacks.length];
for(int i = 0; i < out.length; i++){
out[i] = new ItemStack(stacks[i].item, UI.roundAmount((int)(Math.pow(stacks[i].amount, 1) * 50)));
out[i] = new ItemStack(stacks[i].item, UI.roundAmount((int)(Math.pow(stacks[i].amount, 1.1) * 50)));
}
return out;

View File

@@ -91,7 +91,7 @@ public abstract class Weather extends UnlockableContent{
if(!headless && sound != Sounds.none){
float noise = soundVolOscMag > 0 ? (float)Math.abs(Noise.rawNoise(Time.time() / soundVolOscScl)) * soundVolOscMag : 0;
loops.play(sound, Math.max((soundVol + noise) * state.opacity, soundVolMin));
control.sound.loop(sound, Math.max((soundVol + noise) * state.opacity, soundVolMin));
}
}

View File

@@ -136,7 +136,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
if(to.near().contains(launchSector)) return launchSector;
Sector launchFrom = launchSector;
if(launchFrom == null){
if(launchFrom == null || (to.preset == null && !to.near().contains(launchSector))){
//TODO pick one with the most resources
launchFrom = to.near().find(Sector::hasBase);
if(launchFrom == null && to.preset != null){
@@ -465,7 +465,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
int i = 0;
for(ItemStack stack : items){
res.image(stack.item.icon(Cicon.small)).padRight(3);
res.add(UI.formatAmount(stack.amount)).color(Color.lightGray);
res.add(UI.formatAmount(Math.max(stack.amount, 0))).color(Color.lightGray);
if(++i % 2 == 0){
res.row();
}

View File

@@ -617,7 +617,7 @@ public class Block extends UnlockableContent{
public ItemStack[] researchRequirements(){
ItemStack[] out = new ItemStack[requirements.length];
for(int i = 0; i < out.length; i++){
int quantity = 60 + Mathf.round(Mathf.pow(requirements[i].amount, 1.09f) * 20 * researchCostMultiplier, 10);
int quantity = 60 + Mathf.round(Mathf.pow(requirements[i].amount, 1.11f) * 20 * researchCostMultiplier, 10);
out[i] = new ItemStack(requirements[i].item, UI.roundAmount(quantity));
}

View File

@@ -95,7 +95,9 @@ public class TractorBeamTurret extends BaseTurret{
//look at target
if(target != null && target.within(this, range) && target.team() != team && target.type.flying && efficiency() > 0.01f){
loops.play(shootSound, this, shootSoundVolume);
if(!headless){
control.sound.loop(shootSound, this, shootSoundVolume);
}
any = true;
float dest = angleTo(target);