| package li.cil.oc.client |
| |
| import cpw.mods.fml.client.FMLClientHandler |
| import java.util.logging.Level |
| import java.util.{TimerTask, Timer, UUID} |
| import li.cil.oc.{OpenComputers, Settings} |
| import net.minecraft.client.Minecraft |
| import net.minecraft.tileentity.TileEntity |
| import net.minecraftforge.client.event.sound.SoundLoadEvent |
| import net.minecraftforge.event.ForgeSubscribe |
| import net.minecraftforge.event.world.WorldEvent |
| import paulscode.sound.SoundSystemConfig |
| import scala.collection.mutable |
| |
| object Sound { |
| private val sources = mutable.Map.empty[TileEntity, PseudoLoopingStream] |
| |
| private val commandQueue = mutable.PriorityQueue.empty[Command] |
| |
| private var lastVolume = FMLClientHandler.instance.getClient.gameSettings.soundVolume |
| |
| private val updateTimer = new Timer("OpenComputers-SoundUpdater", true) |
| if (Settings.get.soundVolume > 0) { |
| updateTimer.scheduleAtFixedRate(new TimerTask { |
| override def run() { |
| updateVolume() |
| processQueue() |
| } |
| }, 500, 50) |
| } |
| |
| private def soundSystem = Minecraft.getMinecraft.sndManager.sndSystem |
| |
| private def updateVolume() { |
| val volume = FMLClientHandler.instance.getClient.gameSettings.soundVolume |
| if (volume != lastVolume) { |
| lastVolume = volume |
| sources.synchronized { |
| for (sound <- sources.values) { |
| sound.updateVolume() |
| } |
| } |
| } |
| } |
| |
| private def processQueue() { |
| if (!commandQueue.isEmpty) { |
| commandQueue.synchronized { |
| while (!commandQueue.isEmpty && commandQueue.head.when < System.currentTimeMillis()) { |
| try commandQueue.dequeue()() catch { |
| case t: Throwable => OpenComputers.log.log(Level.WARNING, "Error processing sound command.", t) |
| } |
| } |
| } |
| } |
| } |
| |
| def startLoop(tileEntity: TileEntity, name: String, volume: Float = 1f, delay: Long = 0) { |
| if (Settings.get.soundVolume > 0) { |
| commandQueue.synchronized { |
| commandQueue += new StartCommand(System.currentTimeMillis() + delay, tileEntity, name, volume) |
| } |
| } |
| } |
| |
| def stopLoop(tileEntity: TileEntity) { |
| if (Settings.get.soundVolume > 0) { |
| commandQueue.synchronized { |
| commandQueue += new StopCommand(tileEntity) |
| } |
| } |
| } |
| |
| def updatePosition(tileEntity: TileEntity) { |
| if (Settings.get.soundVolume > 0) { |
| commandQueue.synchronized { |
| commandQueue += new UpdatePositionCommand(tileEntity) |
| } |
| } |
| } |
| |
| @ForgeSubscribe |
| def onSoundLoad(event: SoundLoadEvent) { |
| for (i <- 1 to 6) { |
| event.manager.soundPoolSounds.addSound(Settings.resourceDomain + s":floppy_access$i.ogg") |
| } |
| for (i <- 1 to 7) { |
| event.manager.soundPoolSounds.addSound(Settings.resourceDomain + s":hdd_access$i.ogg") |
| } |
| event.manager.soundPoolSounds.addSound(Settings.resourceDomain + ":floppy_insert.ogg") |
| event.manager.soundPoolSounds.addSound(Settings.resourceDomain + ":floppy_eject.ogg") |
| |
| event.manager.soundPoolSounds.addSound(Settings.resourceDomain + ":computer_running.ogg") |
| } |
| |
| @ForgeSubscribe |
| def onWorldUnload(event: WorldEvent.Unload) { |
| commandQueue.synchronized(commandQueue.clear()) |
| sources.synchronized { |
| sources.foreach(_._2.stop()) |
| } |
| } |
| |
| private abstract class Command(val when: Long, val tileEntity: TileEntity) extends Ordered[Command] { |
| def apply() |
| |
| override def compare(that: Command) = (that.when - when).toInt |
| } |
| |
| private class StartCommand(when: Long, tileEntity: TileEntity, val name: String, val volume: Float) extends Command(when, tileEntity) { |
| override def apply() { |
| sources.synchronized { |
| sources.getOrElseUpdate(tileEntity, new PseudoLoopingStream(tileEntity, volume)).play(name) |
| } |
| } |
| } |
| |
| private class StopCommand(tileEntity: TileEntity) extends Command(0, tileEntity) { |
| override def apply() { |
| sources.synchronized { |
| sources.remove(tileEntity) match { |
| case Some(sound) => sound.stop() |
| case _ => |
| } |
| } |
| commandQueue.synchronized { |
| // Remove all other commands for this tile entity from the queue. This |
| // is inefficient, but we generally don't expect the command queue to |
| // be very long, so this should be OK. |
| commandQueue ++= commandQueue.dequeueAll.filter(_.tileEntity != tileEntity) |
| } |
| } |
| } |
| |
| private class UpdatePositionCommand(tileEntity: TileEntity) extends Command(0, tileEntity) { |
| override def apply() { |
| sources.synchronized { |
| sources.get(tileEntity) match { |
| case Some(sound) => sound.updatePosition() |
| case _ => |
| } |
| } |
| } |
| } |
| |
| private class PseudoLoopingStream(val tileEntity: TileEntity, val volume: Float, val source: String = UUID.randomUUID.toString) { |
| var initialized = false |
| |
| def updateVolume() { |
| soundSystem.setVolume(source, lastVolume * volume * Settings.get.soundVolume) |
| } |
| |
| def updatePosition() { |
| soundSystem.setPosition(source, tileEntity.xCoord, tileEntity.yCoord, tileEntity.zCoord) |
| } |
| |
| def play(name: String) { |
| val resourceName = s"${Settings.resourceDomain}:$name" |
| val sound = Minecraft.getMinecraft.sndManager.soundPoolSounds.getRandomSoundFromSoundPool(resourceName) |
| if (!initialized) { |
| initialized = true |
| soundSystem.newSource(false, source, sound.getSoundUrl, sound.getSoundName, true, tileEntity.xCoord, tileEntity.yCoord, tileEntity.zCoord, SoundSystemConfig.ATTENUATION_LINEAR, 16.0f) |
| updateVolume() |
| soundSystem.activate(source) |
| } |
| soundSystem.play(source) |
| } |
| |
| def stop() { |
| if (soundSystem != null) { |
| soundSystem.stop(source) |
| soundSystem.removeSource(source) |
| } |
| } |
| } |
| |
| } |