Moved to ocelot sound system

This commit is contained in:
IgorTimofeev 2023-04-18 00:45:59 +03:00
parent 3f07bae62c
commit 6b6c70ee73
12 changed files with 386 additions and 486 deletions

@ -1 +1 @@
Subproject commit f40c686e2a012807f23662f987b90f3f73df21a0 Subproject commit 40ec8c7014a1d4607ee0c31488783ae82c8827a5

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -1,156 +1,169 @@
BackgroundPattern 0 0 304 304 BackgroundPattern 0 0 304 304
BarSegment 29 355 16 4 BarSegment 495 305 16 4
ComputerMotherboard 305 0 79 70 ComputerMotherboard 233 305 79 70
Empty 12 414 1 1 Empty 495 339 1 1
EmptySlot 458 147 18 18 EmptySlot 322 129 18 18
Knob 385 0 50 50 Knob 313 305 50 50
KnobCenter 436 0 50 50 KnobCenter 364 305 50 50
KnobLimits 305 71 50 50 KnobLimits 415 305 50 50
ShadowBorder 0 394 1 24 ShadowBorder 491 25 1 24
ShadowCorner 441 122 24 24 ShadowCorner 441 0 24 24
TabArrow 0 340 8 14 TabArrow 441 75 8 14
buttons/BottomDrawerClose 477 147 18 18 buttons/BottomDrawerClose 341 129 18 18
buttons/BottomDrawerOpen 441 180 18 18 buttons/BottomDrawerOpen 360 129 18 18
buttons/PowerOff 460 180 18 18 buttons/OpenFMRadioCloseOff 478 325 7 8
buttons/PowerOn 479 180 18 18 buttons/OpenFMRadioCloseOn 486 325 7 8
icons/ButtonCheck 466 122 17 17 buttons/OpenFMRadioRedstoneOff 469 316 8 8
icons/ButtonClipboard 484 122 17 17 buttons/OpenFMRadioRedstoneOn 478 316 8 8
icons/ButtonRandomize 373 219 17 17 buttons/OpenFMRadioStartOff 466 0 24 24
icons/CPU 305 251 16 16 buttons/OpenFMRadioStartOn 441 25 24 24
icons/Card 322 251 16 16 buttons/OpenFMRadioStopOff 466 25 24 24
icons/ComponentBus 339 251 16 16 buttons/OpenFMRadioStopOn 441 50 24 24
icons/DragLMB 441 199 21 14 buttons/OpenFMRadioVolumeDownOff 450 75 10 10
icons/DragRMB 463 199 21 14 buttons/OpenFMRadioVolumeDownOn 461 75 10 10
icons/EEPROM 356 251 16 16 buttons/OpenFMRadioVolumeUpOff 472 75 10 10
icons/Floppy 373 251 16 16 buttons/OpenFMRadioVolumeUpOn 483 75 10 10
icons/HDD 390 251 16 16 buttons/PowerOff 379 129 18 18
icons/LMB 26 322 11 14 buttons/PowerOn 398 129 18 18
icons/Memory 407 251 16 16 icons/ButtonCheck 305 162 17 17
icons/NA 424 251 16 16 icons/ButtonClipboard 323 162 17 17
icons/NotificationError 50 322 11 11 icons/ButtonRandomize 341 162 17 17
icons/NotificationInfo 62 322 11 11 icons/CPU 305 180 16 16
icons/NotificationWarning 74 322 11 11 icons/Card 322 180 16 16
icons/RMB 38 322 11 14 icons/ComponentBus 339 180 16 16
icons/RackMountable 441 251 16 16 icons/DragLMB 417 129 21 14
icons/SettingsSound 0 322 12 17 icons/DragRMB 439 129 21 14
icons/SettingsUI 13 322 12 17 icons/EEPROM 356 180 16 16
icons/Tier0 458 251 16 16 icons/Floppy 373 180 16 16
icons/Tier1 475 251 16 16 icons/HDD 390 180 16 16
icons/Tier2 492 251 16 16 icons/LMB 492 50 11 14
icons/WaveLFSR 70 305 24 10 icons/Memory 407 180 16 16
icons/WaveNoise 95 305 24 10 icons/NA 424 180 16 16
icons/WaveSawtooth 120 305 24 10 icons/NotificationError 473 129 11 11
icons/WaveSine 145 305 24 10 icons/NotificationInfo 485 129 11 11
icons/WaveSquare 170 305 24 10 icons/NotificationWarning 497 129 11 11
icons/WaveTriangle 195 305 24 10 icons/RMB 461 129 11 14
icons/WireArrowLeft 2 394 4 8 icons/RackMountable 441 180 16 16
icons/WireArrowRight 7 394 4 8 icons/SettingsSound 466 50 12 17
items/APU0 373 122 16 96 icons/SettingsUI 479 50 12 17
items/APU1 390 122 16 96 icons/Tier0 458 180 16 16
items/APU2 407 122 16 96 icons/Tier1 475 180 16 16
items/CPU0 305 268 16 16 icons/Tier2 492 180 16 16
items/CPU1 322 268 16 16 icons/WaveLFSR 392 282 24 10
items/CPU2 339 268 16 16 icons/WaveNoise 417 282 24 10
items/CardBase 356 268 16 16 icons/WaveSawtooth 442 282 24 10
items/CircuitBoard 373 268 16 16 icons/WaveSine 467 282 24 10
items/ComponentBus0 390 268 16 16 icons/WaveSquare 380 162 24 10
items/ComponentBus1 407 268 16 16 icons/WaveTriangle 405 162 24 10
items/ComponentBus2 424 268 16 16 icons/WireArrowLeft 493 25 4 8
items/ComponentBus3 441 268 16 16 icons/WireArrowRight 498 25 4 8
items/DataCard0 305 122 16 128 items/APU0 373 0 16 96
items/DataCard1 322 122 16 128 items/APU1 390 0 16 96
items/DataCard2 339 122 16 128 items/APU2 407 0 16 96
items/DebugCard 458 268 16 16 items/CPU0 305 197 16 16
items/DiskDriveMountable 475 268 16 16 items/CPU1 322 197 16 16
items/EEPROM 492 268 16 16 items/CPU2 339 197 16 16
items/FloppyDisk_dyeBlack 305 285 16 16 items/CardBase 356 197 16 16
items/FloppyDisk_dyeBlue 322 285 16 16 items/CircuitBoard 373 197 16 16
items/FloppyDisk_dyeBrown 339 285 16 16 items/ComponentBus0 390 197 16 16
items/FloppyDisk_dyeCyan 356 285 16 16 items/ComponentBus1 407 197 16 16
items/FloppyDisk_dyeGray 373 285 16 16 items/ComponentBus2 424 197 16 16
items/FloppyDisk_dyeGreen 390 285 16 16 items/ComponentBus3 441 197 16 16
items/FloppyDisk_dyeLightBlue 407 285 16 16 items/DataCard0 305 0 16 128
items/FloppyDisk_dyeLightGray 424 285 16 16 items/DataCard1 322 0 16 128
items/FloppyDisk_dyeLime 441 285 16 16 items/DataCard2 339 0 16 128
items/FloppyDisk_dyeMagenta 458 285 16 16 items/DebugCard 458 197 16 16
items/FloppyDisk_dyeOrange 475 285 16 16 items/DiskDriveMountable 475 197 16 16
items/FloppyDisk_dyePink 492 285 16 16 items/EEPROM 492 197 16 16
items/FloppyDisk_dyePurple 356 71 16 16 items/FloppyDisk_dyeBlack 305 214 16 16
items/FloppyDisk_dyeRed 373 71 16 16 items/FloppyDisk_dyeBlue 322 214 16 16
items/FloppyDisk_dyeWhite 390 71 16 16 items/FloppyDisk_dyeBrown 339 214 16 16
items/FloppyDisk_dyeYellow 407 71 16 16 items/FloppyDisk_dyeCyan 356 214 16 16
items/GraphicsCard0 424 71 16 16 items/FloppyDisk_dyeGray 373 214 16 16
items/GraphicsCard1 441 71 16 16 items/FloppyDisk_dyeGreen 390 214 16 16
items/GraphicsCard2 458 71 16 16 items/FloppyDisk_dyeLightBlue 407 214 16 16
items/HardDiskDrive0 475 71 16 16 items/FloppyDisk_dyeLightGray 424 214 16 16
items/HardDiskDrive1 492 71 16 16 items/FloppyDisk_dyeLime 441 214 16 16
items/HardDiskDrive2 356 88 16 16 items/FloppyDisk_dyeMagenta 458 214 16 16
items/InternetCard 441 147 16 32 items/FloppyDisk_dyeOrange 475 214 16 16
items/LinkedCard 424 122 16 96 items/FloppyDisk_dyePink 492 214 16 16
items/Memory0 373 88 16 16 items/FloppyDisk_dyePurple 305 231 16 16
items/Memory1 390 88 16 16 items/FloppyDisk_dyeRed 322 231 16 16
items/Memory2 407 88 16 16 items/FloppyDisk_dyeWhite 339 231 16 16
items/Memory3 424 88 16 16 items/FloppyDisk_dyeYellow 356 231 16 16
items/Memory4 441 88 16 16 items/GraphicsCard0 373 231 16 16
items/Memory5 458 88 16 16 items/GraphicsCard1 390 231 16 16
items/NetworkCard 475 88 16 16 items/GraphicsCard2 407 231 16 16
items/RedstoneCard0 492 88 16 16 items/HardDiskDrive0 424 231 16 16
items/RedstoneCard1 356 105 16 16 items/HardDiskDrive1 441 231 16 16
items/Server0 373 105 16 16 items/HardDiskDrive2 458 231 16 16
items/Server1 390 105 16 16 items/InternetCard 305 129 16 32
items/Server2 407 105 16 16 items/LinkedCard 424 0 16 96
items/Server3 424 105 16 16 items/Memory0 475 231 16 16
items/SoundCard 356 122 16 128 items/Memory1 492 231 16 16
items/WirelessNetworkCard0 441 105 16 16 items/Memory2 305 248 16 16
items/WirelessNetworkCard1 458 105 16 16 items/Memory3 322 248 16 16
light-panel/BookmarkLeft 51 305 18 14 items/Memory4 339 248 16 16
light-panel/BookmarkRight 391 219 20 14 items/Memory5 356 248 16 16
light-panel/BorderB 8 403 4 4 items/NetworkCard 373 248 16 16
light-panel/BorderL 98 403 4 2 items/RedstoneCard0 390 248 16 16
light-panel/BorderR 13 403 4 4 items/RedstoneCard1 407 248 16 16
light-panel/BorderT 18 403 4 4 items/Server0 424 248 16 16
light-panel/CornerBL 23 403 4 4 items/Server1 441 248 16 16
light-panel/CornerBR 28 403 4 4 items/Server2 458 248 16 16
light-panel/CornerTL 33 403 4 4 items/Server3 475 248 16 16
light-panel/CornerTR 38 403 4 4 items/SoundCard 356 0 16 128
light-panel/Fill 6 414 2 2 items/WirelessNetworkCard0 492 248 16 16
light-panel/Vent 0 355 2 38 items/WirelessNetworkCard1 305 265 16 16
nodes/Cable 3 366 8 8 light-panel/BookmarkLeft 373 282 18 14
nodes/Camera 475 105 16 16 light-panel/BookmarkRight 359 162 20 14
nodes/Computer 492 105 16 16 light-panel/BorderB 499 34 4 4
nodes/ComputerActivityOverlay 487 0 16 16 light-panel/BorderL 479 339 4 2
nodes/ComputerErrorOverlay 487 17 16 16 light-panel/BorderR 504 34 4 4
nodes/ComputerOnOverlay 487 34 16 16 light-panel/BorderT 493 45 4 4
nodes/DiskDrive 385 51 16 16 light-panel/CornerBL 498 45 4 4
nodes/DiskDriveActivity 402 51 16 16 light-panel/CornerBR 503 45 4 4
nodes/DiskDriveFloppy 419 51 16 16 light-panel/CornerTL 503 25 4 4
nodes/IronNoteBlock 436 51 16 16 light-panel/CornerTR 502 325 4 4
nodes/NewNode 453 51 16 16 light-panel/Fill 489 339 2 2
nodes/NoteBlock 470 51 16 16 light-panel/Vent 466 305 2 38
nodes/OpenFMRadio 487 51 16 16 nodes/Cable 487 316 8 8
nodes/Relay 0 305 16 16 nodes/Camera 322 265 16 16
nodes/Screen 17 305 16 16 nodes/Computer 339 265 16 16
nodes/ScreenOnOverlay 34 305 16 16 nodes/ComputerActivityOverlay 356 265 16 16
panel/BorderB 43 403 4 4 nodes/ComputerErrorOverlay 373 265 16 16
panel/BorderL 103 403 4 2 nodes/ComputerOnOverlay 390 265 16 16
panel/BorderR 48 403 4 4 nodes/DiskDrive 407 265 16 16
panel/BorderT 53 403 4 4 nodes/DiskDriveActivity 424 265 16 16
panel/CornerBL 58 403 4 4 nodes/DiskDriveFloppy 441 265 16 16
panel/CornerBR 63 403 4 4 nodes/IronNoteBlock 458 265 16 16
panel/CornerTL 68 403 4 4 nodes/NewNode 475 265 16 16
panel/CornerTR 73 403 4 4 nodes/NoteBlock 492 265 16 16
panel/Fill 9 414 2 2 nodes/OpenFMRadio 305 282 16 16
particles/Note 21 355 7 10 nodes/Relay 322 282 16 16
screen/BorderB 5 403 2 8 nodes/Screen 339 282 16 16
screen/BorderT 2 403 2 10 nodes/ScreenOnOverlay 356 282 16 16
screen/CornerBL 12 366 8 8 panel/BorderB 507 325 4 4
screen/CornerBR 21 366 8 8 panel/BorderL 484 339 4 2
screen/CornerTL 3 355 8 10 panel/BorderR 469 334 4 4
screen/CornerTR 12 355 8 10 panel/BorderT 474 334 4 4
window/BorderDark 2 414 1 4 panel/CornerBL 479 334 4 4
window/BorderLight 4 414 1 4 panel/CornerBR 484 334 4 4
window/CloseButton 30 366 7 6 panel/CornerTL 489 334 4 4
window/CornerBL 78 403 4 4 panel/CornerTR 494 334 4 4
window/CornerBR 83 403 4 4 panel/Fill 492 339 2 2
window/CornerTL 88 403 4 4 particles/Note 487 305 7 10
window/CornerTR 93 403 4 4 screen/BorderB 496 34 2 8
screen/BorderT 493 34 2 10
screen/CornerBL 496 316 8 8
screen/CornerBR 469 325 8 8
screen/CornerTL 469 305 8 10
screen/CornerTR 478 305 8 10
window/BorderDark 509 334 1 4
window/BorderLight 508 25 1 4
window/CloseButton 494 325 7 6
window/CornerBL 499 334 4 4
window/CornerBR 504 334 4 4
window/CornerTL 469 339 4 4
window/CornerTR 474 339 4 4
window/OpenFMRadio 0 305 232 105

View File

@ -1,7 +1,9 @@
package ocelot.desktop.entity.openfmradio package ocelot.desktop.entity
import ocelot.desktop.audio.{Audio, SoundCategory} import ocelot.desktop.audio.SoundSamples.Format
import ocelot.desktop.audio.{Audio, SoundCategory, SoundSamples, SoundSource}
import ocelot.desktop.color.IntColor import ocelot.desktop.color.IntColor
import org.lwjgl.BufferUtils
import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context} import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context}
import totoro.ocelot.brain.entity.traits.{DeviceInfo, Entity, Environment} import totoro.ocelot.brain.entity.traits.{DeviceInfo, Entity, Environment}
import totoro.ocelot.brain.nbt.NBTTagCompound import totoro.ocelot.brain.nbt.NBTTagCompound
@ -9,7 +11,11 @@ import totoro.ocelot.brain.network.{Component, Network, Visibility}
import totoro.ocelot.brain.util.ResultWrapper.result import totoro.ocelot.brain.util.ResultWrapper.result
import totoro.ocelot.brain.workspace.Workspace import totoro.ocelot.brain.workspace.Workspace
import java.io.BufferedInputStream
import java.net.{HttpURLConnection, URI} import java.net.{HttpURLConnection, URI}
import java.nio.ByteOrder
import javax.sound.sampled.AudioFormat.Encoding
import javax.sound.sampled.{AudioFormat, AudioSystem}
class OpenFMRadio extends Entity with Environment with DeviceInfo { class OpenFMRadio extends Entity with Environment with DeviceInfo {
override val node: Component = override val node: Component =
@ -20,52 +26,118 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
private val defaultScreenText = "OpenFM" private val defaultScreenText = "OpenFM"
private val defaultScreenColor = IntColor(0x0AFF0A) private val defaultScreenColor = IntColor(0x0AFF0A)
var url: Option[String] = None
var screenColor: IntColor = defaultScreenColor var screenColor: IntColor = defaultScreenColor
var screenText: String = defaultScreenText var screenText: String = defaultScreenText
private var player: Option[StreamAudioPlayer] = None private var thread: Option[Thread] = None
private var playerThread: Option[Thread] = None
private var _volume: Float = 1 private var _volume: Float = 1
private var _url: Option[String] = None
def start(): Boolean = { private var soundSource: Option[SoundSource] = None
def url: Option[String] = _url
def url_=(value: Option[String]): Unit = {
_url =
if (value.isDefined)
Option(value.get.take(32))
else
value
}
private def playSynchronously(): Unit = {
try {
// Trying to parse URL and sending request to host
val connection = new URI(url.get).toURL.openConnection.asInstanceOf[HttpURLConnection]
connection.setDoInput(true)
connection.setDoOutput(true)
connection.setRequestMethod("GET")
// Wrapping stream to another one with hardcoded format
val inputStream = AudioSystem.getAudioInputStream(
new AudioFormat(
Encoding.PCM_SIGNED,
44100,
16,
2,
4,
44100,
false
),
// Obtaining audio input stream from HTTP connection
AudioSystem.getAudioInputStream(new BufferedInputStream(connection.getInputStream))
)
// Keeping input stream format parameters here to offload the reading loop
val inputStreamFormat = inputStream.getFormat
val inputStreamSampleRate = inputStreamFormat.getSampleRate.toInt
val inputStreamSoundSampleFormat =
if (inputStreamFormat.getChannels > 1)
Format.Stereo16
else
Format.Mono16
// Creating Ocelot audio output stream
val (outputStream, source) = Audio.newStream(SoundCategory.Environment, volume = volume)
soundSource = Option(source)
// Reading chunks from input stream and passing them to output
val buffer = new Array[Byte](65536)
var bytesRead = 0
while (!Thread.currentThread().isInterrupted && bytesRead != -1) {
if (bytesRead > 0) {
outputStream.enqueue(new SoundSamples(
BufferUtils
.createByteBuffer(bytesRead)
.order(ByteOrder.LITTLE_ENDIAN)
.put(buffer, 0, bytesRead)
.flip,
inputStreamSampleRate,
inputStreamSoundSampleFormat
))
}
bytesRead = inputStream.read(buffer, 0, buffer.length)
}
}
catch {
case _: InterruptedException =>
case e: Exception => e.printStackTrace()
}
this.synchronized {
if (soundSource.isDefined) {
soundSource.get.stop()
soundSource = None
}
if (thread.isDefined)
thread = None
}
}
def play(): Boolean = {
if (url.isEmpty) if (url.isEmpty)
return false return false
if (isPlaying) if (isPlaying)
return true return true
this.playerThread = Option(new Thread(() => { this.synchronized {
try { thread = Option(new Thread(() => playSynchronously()))
val connection = new URI(url.get).toURL.openConnection.asInstanceOf[HttpURLConnection] thread.get.setPriority(Thread.MIN_PRIORITY)
connection.setDoInput(true) thread.get.start()
connection.setDoOutput(true) }
connection.setRequestMethod("GET")
player = Option(new StreamAudioPlayer(connection.getInputStream))
player.get.volume = volume
player.get.start()
}
catch {
case e: Exception =>
stop()
e.printStackTrace()
}
}))
playerThread.get.setPriority(Thread.MIN_PRIORITY)
playerThread.get.start()
true true
} }
def stop(): Boolean = { def stop(): Boolean = {
if (player.isDefined) { this.synchronized {
player.get.stop() if (thread.isDefined)
thread.get.interrupt()
player = None
playerThread = None
} }
true true
@ -76,17 +148,12 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
def volume_=(value: Float): Unit = { def volume_=(value: Float): Unit = {
_volume = value _volume = value
if (player.isDefined) if (soundSource.isDefined)
player.get.volume = value soundSource.get.volume = value
} }
private def isPlaying: Boolean = { def isPlaying: Boolean = {
if (player.isDefined) thread.isDefined
player.get.isPlaying
else if (playerThread.isDefined)
playerThread.get.isAlive
else
false
} }
override def dispose(): Unit = { override def dispose(): Unit = {
@ -153,7 +220,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
@Callback() @Callback()
def start(context: Context, args: Arguments): Array[AnyRef] = { def start(context: Context, args: Arguments): Array[AnyRef] = {
result(start()) result(play())
} }
@Callback() @Callback()
@ -221,7 +288,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
// Playing again if previously saved value was true // Playing again if previously saved value was true
if (nbt.hasKey("isPlaying") && nbt.getBoolean("isPlaying")) if (nbt.hasKey("isPlaying") && nbt.getBoolean("isPlaying"))
start() play()
} }
override def save(nbt: NBTTagCompound): Unit = { override def save(nbt: NBTTagCompound): Unit = {

View File

@ -1,104 +0,0 @@
package ocelot.desktop.entity.openfmradio
import java.io.FilterInputStream
import java.io.IOException
import java.io.InputStream
/*
* An Erroring InputStream when mark limits are exceeded
* Fixes MP3SPI infinitely reading until it finds a false positive
*/
class MarkErrorInputStream(proxy: InputStream) extends FilterInputStream(proxy) {
if (!proxy.markSupported)
throw new IllegalArgumentException("input stream does not support mark")
private var position = 0L
private var markLimit = 0L
private var markActive = false
@throws[IOException]
private def limit(value: Long): Long = {
if (markActive) {
val avail = markLimit - position
if (avail <= 0) {
in.reset()
throw new IOException("mark limit exceeded")
}
else if (avail < value) {
return avail
}
}
value
}
@throws[IOException]
private def addPos(n: Long): Unit = {
if (n >= 0 && markActive)
position += n
}
@throws[IOException]
override def read: Int = {
limit(1)
val result = super.read
addPos(1)
result
}
@throws[IOException]
override def read(bts: Array[Byte]): Int = {
val result = super.read(bts, 0, limit(bts.length).toInt)
addPos(result)
result
}
@throws[IOException]
override def read(bts: Array[Byte], off: Int, len: Int): Int = {
val result = super.read(bts, off, limit(len).toInt)
addPos(result)
result
}
@throws[IOException]
override def skip(ln: Long): Long = {
val result = super.skip(limit(ln))
addPos(result)
result
}
override def mark(readLimit: Int): Unit = {
var newLimit = readLimit
// prevent mp3spi's insane limit of 4096001.
if (newLimit == 4096001)
newLimit = 4096
super.mark(newLimit)
markActive = true
markLimit = newLimit
position = 0
}
@throws[IOException]
override def reset(): Unit = {
super.reset()
markActive = false
}
@throws[IOException]
override def available: Int = {
// Allows BufferedInputStream to stop reading.
if (markActive && position >= markLimit)
return 0
limit(super.available).toInt
}
}

View File

@ -1,168 +0,0 @@
package ocelot.desktop.entity.openfmradio
import ocelot.desktop.audio.SoundCategory
import java.io.BufferedInputStream
import java.io.IOException
import java.io.InputStream
import java.nio.ByteOrder
import java.nio.IntBuffer
import javax.sound.sampled.AudioFormat
import javax.sound.sampled.AudioFormat.Encoding
import javax.sound.sampled.AudioInputStream
import javax.sound.sampled.AudioSystem
import javax.sound.sampled.UnsupportedAudioFileException
import org.lwjgl.BufferUtils
import org.lwjgl.openal.AL10
class StreamAudioPlayer(var stream: AudioInputStream) {
def this(inputStream: InputStream) {
this(AudioSystem.getAudioInputStream(new MarkErrorInputStream(new BufferedInputStream(inputStream))))
}
private var buffer: IntBuffer = _
private var source: IntBuffer = _
private var _volume = 1f
private var _isPlaying = false
def isPlaying: Boolean = _isPlaying
def isPlaying_=(value: Boolean): Unit = _isPlaying = value
def volume: Float = _volume
def volume_=(value: Float): Unit = {
_volume = value
if (isPlaying && source != null)
UpdateALGain()
}
private def hasALError: Boolean = {
AL10.alGetError != AL10.AL_NO_ERROR
}
private def getOutFormat = new AudioFormat(
Encoding.PCM_SIGNED,
44100,
16,
2,
4,
44100,
false
)
@throws[UnsupportedAudioFileException]
@throws[IOException]
def start(): Unit = {
if (isPlaying)
return
val outFormat = getOutFormat
source = BufferUtils.createIntBuffer(1)
AL10.alGenSources(source)
if (hasALError) {
stop()
return
}
AL10.alSourcei(source.get(0), AL10.AL_LOOPING, AL10.AL_FALSE)
AL10.alSourcef(source.get(0), AL10.AL_PITCH, 1)
UpdateALGain()
if (hasALError) {
stop()
return
}
isPlaying = true
stream(AudioSystem.getAudioInputStream(outFormat, stream))
if (isPlaying) {
while (AL10.alGetSourcei(source.get(0), AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING) {
try {
Thread.sleep(1)
}
catch {
case _: InterruptedException =>
}
}
}
stop()
}
@throws[IOException]
private def stream(in: AudioInputStream): Unit = {
val format = in.getFormat
val dataBuffer = new Array[Byte](65536)
var n = 0
while (isPlaying && n != -1) {
if (n > 0) {
if (buffer == null) {
buffer = BufferUtils.createIntBuffer(1)
}
else {
val processed = AL10.alGetSourcei(source.get(0), AL10.AL_BUFFERS_PROCESSED)
if (processed > 0) {
AL10.alSourceUnqueueBuffers(source.get(0), buffer)
}
}
AL10.alGenBuffers(buffer)
val data = BufferUtils.createByteBuffer(n).order(ByteOrder.LITTLE_ENDIAN).put(dataBuffer, 0, n).flip
AL10.alBufferData(
buffer.get(0),
if (format.getChannels > 1)
AL10.AL_FORMAT_STEREO16
else
AL10.AL_FORMAT_MONO16,
data,
format.getSampleRate.toInt
)
AL10.alSourceQueueBuffers(source.get(0), buffer)
val state = AL10.alGetSourcei(source.get(0), AL10.AL_SOURCE_STATE)
if (isPlaying && state != AL10.AL_PLAYING)
AL10.alSourcePlay(source.get(0))
}
n = in.read(dataBuffer, 0, dataBuffer.length)
}
}
def stop(): Unit = {
if (!isPlaying)
return
isPlaying = false
if (source != null) {
AL10.alSourceStop(source)
AL10.alDeleteSources(source)
source = null
}
if (buffer != null) {
AL10.alDeleteBuffers(buffer)
buffer = null
}
}
private def UpdateALGain(): Unit = {
AL10.alSourcef(
source.get(0),
AL10.AL_GAIN,
volume * SoundCategory.getSettingsValue(SoundCategory.Environment)
)
}
}

View File

@ -3,8 +3,7 @@ package ocelot.desktop.node
import ocelot.desktop.node.nodes._ import ocelot.desktop.node.nodes._
import totoro.ocelot.brain.entity.traits.GenericCamera import totoro.ocelot.brain.entity.traits.GenericCamera
import totoro.ocelot.brain.entity.{Cable, Case, FloppyDiskDrive, IronNoteBlock, NoteBlock, Relay, Screen} import totoro.ocelot.brain.entity.{Cable, Case, FloppyDiskDrive, IronNoteBlock, NoteBlock, Relay, Screen}
import ocelot.desktop.entity.Camera import ocelot.desktop.entity.{Camera, OpenFMRadio}
import ocelot.desktop.entity.openfmradio.OpenFMRadio
import scala.collection.mutable import scala.collection.mutable

View File

@ -2,9 +2,10 @@ package ocelot.desktop.node.nodes
import ocelot.desktop.OcelotDesktop import ocelot.desktop.OcelotDesktop
import ocelot.desktop.color.{Color, RGBAColorNorm} import ocelot.desktop.color.{Color, RGBAColorNorm}
import ocelot.desktop.entity.openfmradio.OpenFMRadio import ocelot.desktop.entity.OpenFMRadio
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.Node import ocelot.desktop.node.Node
import ocelot.desktop.windows.{CameraWindow, OpenFMRadioWindow}
class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends Node(openFMRadio) { class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends Node(openFMRadio) {
override def icon: String = "nodes/OpenFMRadio" override def icon: String = "nodes/OpenFMRadio"
@ -73,4 +74,11 @@ class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends Node(openFMRadio) {
openFMRadio.stop() openFMRadio.stop()
} }
private var currentWindow: Option[OpenFMRadioWindow] = None
override def window: Option[OpenFMRadioWindow] = {
currentWindow = Option(currentWindow.getOrElse(new OpenFMRadioWindow(this)))
currentWindow
}
} }

View File

@ -34,12 +34,26 @@ class IconButton(
if (state == HoverEvent.State.Enter) onHoverEnter() if (state == HoverEvent.State.Enter) onHoverEnter()
else onHoverLeave() else onHoverLeave()
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) => case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) => {
if (!isSwitch || !isOn) press() else release() if (isSwitch) {
clickSoundSource.play() if (isOn) {
release()
}
else {
press()
}
}
else {
press()
}
case ClickEvent(MouseEvent.Button.Left, _) => clickSoundSource.play()
if (!isSwitch || !isOn) release() else press() }
case ClickEvent(MouseEvent.Button.Left, _) => {
if (!isSwitch)
release()
}
} }
def isOn: Boolean = _isOn def isOn: Boolean = _isOn
@ -56,20 +70,29 @@ class IconButton(
if (labelTooltip.isDefined) if (labelTooltip.isDefined)
root.get.tooltipPool.closeTooltip(labelTooltip.get) root.get.tooltipPool.closeTooltip(labelTooltip.get)
def press(): Unit = { def playPressAnimation(): Unit = {
colorAnimation.goto(pressedColor) colorAnimation.goto(pressedColor)
alphaAnimation.goto(1f) alphaAnimation.goto(1f)
_isOn = true _isOn = true
}
def playReleaseAnimation(): Unit = {
colorAnimation.goto(releasedColor)
alphaAnimation.goto(0f)
_isOn = false
}
def press(): Unit = {
playPressAnimation()
onPressed() onPressed()
} }
def release(): Unit = { def release(): Unit = {
colorAnimation.goto(releasedColor) playReleaseAnimation()
alphaAnimation.goto(0f)
_isOn = false
onReleased() onReleased()
} }
size = minimumSize size = minimumSize
private def releasedIconSize: Size2D = Spritesheet.spriteSize(releasedIcon) * sizeMultiplier private def releasedIconSize: Size2D = Spritesheet.spriteSize(releasedIcon) * sizeMultiplier
@ -100,8 +123,8 @@ class IconButton(
super.update() super.update()
colorAnimation.update() colorAnimation.update()
alphaAnimation.update() alphaAnimation.update()
if (isOn && !_isOn) press() if (isOn && !_isOn) playPressAnimation()
if (!isOn && _isOn) release() if (!isOn && _isOn) playReleaseAnimation()
} }
override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClick override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClick

View File

@ -201,7 +201,7 @@ class ComputerWindow(computerNode: ComputerNode) extends BasicWindow {
override def load(nbt: NBTTagCompound): Unit = { override def load(nbt: NBTTagCompound): Unit = {
bottomDrawerAnimation.load(nbt, "drawerAnimation") bottomDrawerAnimation.load(nbt, "drawerAnimation")
if (bottomDrawerAnimation.isGoingUp) drawerButton.press() if (bottomDrawerAnimation.isGoingUp) drawerButton.playPressAnimation()
super.load(nbt) super.load(nbt)
} }
} }

View File

@ -0,0 +1,62 @@
package ocelot.desktop.windows
import ocelot.desktop.audio.{SoundSource, SoundSources}
import ocelot.desktop.color.Color
import ocelot.desktop.entity.OpenFMRadio
import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.nodes.{ComputerNode, OpenFMRadioNode}
import ocelot.desktop.ui.event.MouseEvent
import ocelot.desktop.ui.layout.{Layout, LinearLayout}
import ocelot.desktop.ui.widget._
import ocelot.desktop.ui.widget.tooltip.Tooltip
import ocelot.desktop.ui.widget.window.BasicWindow
import ocelot.desktop.util.animation.UnitAnimation
import ocelot.desktop.util.{DrawUtils, Orientation}
import ocelot.desktop.{ColorScheme, OcelotDesktop}
import totoro.ocelot.brain.entity.Case
import totoro.ocelot.brain.nbt.NBTTagCompound
class OpenFMRadioWindow(radioNode: OpenFMRadioNode) extends BasicWindow {
private def radio: OpenFMRadio = radioNode.openFMRadio
children :+= new Widget {
override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical)
children :+= new PaddingBox(
new Widget {
children :+= new IconButton(
"buttons/PowerOff",
"buttons/PowerOn",
isSwitch = true
) {
override def isOn: Boolean = radio.isPlaying
override def onPressed(): Unit = {
radio.play()
}
override def onReleased(): Unit = {
radio.stop()
}
protected override def clickSoundSource: SoundSource = SoundSources.MinecraftClick
}
},
Padding2D(top = 44, left = 22)
)
}
override def update(): Unit = {
super.update()
}
override def draw(g: Graphics): Unit = {
beginDraw(g)
DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 1f, 0.5f)
drawChildren(g)
endDraw(g)
}
}