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
BarSegment 29 355 16 4
ComputerMotherboard 305 0 79 70
Empty 12 414 1 1
EmptySlot 458 147 18 18
Knob 385 0 50 50
KnobCenter 436 0 50 50
KnobLimits 305 71 50 50
ShadowBorder 0 394 1 24
ShadowCorner 441 122 24 24
TabArrow 0 340 8 14
buttons/BottomDrawerClose 477 147 18 18
buttons/BottomDrawerOpen 441 180 18 18
buttons/PowerOff 460 180 18 18
buttons/PowerOn 479 180 18 18
icons/ButtonCheck 466 122 17 17
icons/ButtonClipboard 484 122 17 17
icons/ButtonRandomize 373 219 17 17
icons/CPU 305 251 16 16
icons/Card 322 251 16 16
icons/ComponentBus 339 251 16 16
icons/DragLMB 441 199 21 14
icons/DragRMB 463 199 21 14
icons/EEPROM 356 251 16 16
icons/Floppy 373 251 16 16
icons/HDD 390 251 16 16
icons/LMB 26 322 11 14
icons/Memory 407 251 16 16
icons/NA 424 251 16 16
icons/NotificationError 50 322 11 11
icons/NotificationInfo 62 322 11 11
icons/NotificationWarning 74 322 11 11
icons/RMB 38 322 11 14
icons/RackMountable 441 251 16 16
icons/SettingsSound 0 322 12 17
icons/SettingsUI 13 322 12 17
icons/Tier0 458 251 16 16
icons/Tier1 475 251 16 16
icons/Tier2 492 251 16 16
icons/WaveLFSR 70 305 24 10
icons/WaveNoise 95 305 24 10
icons/WaveSawtooth 120 305 24 10
icons/WaveSine 145 305 24 10
icons/WaveSquare 170 305 24 10
icons/WaveTriangle 195 305 24 10
icons/WireArrowLeft 2 394 4 8
icons/WireArrowRight 7 394 4 8
items/APU0 373 122 16 96
items/APU1 390 122 16 96
items/APU2 407 122 16 96
items/CPU0 305 268 16 16
items/CPU1 322 268 16 16
items/CPU2 339 268 16 16
items/CardBase 356 268 16 16
items/CircuitBoard 373 268 16 16
items/ComponentBus0 390 268 16 16
items/ComponentBus1 407 268 16 16
items/ComponentBus2 424 268 16 16
items/ComponentBus3 441 268 16 16
items/DataCard0 305 122 16 128
items/DataCard1 322 122 16 128
items/DataCard2 339 122 16 128
items/DebugCard 458 268 16 16
items/DiskDriveMountable 475 268 16 16
items/EEPROM 492 268 16 16
items/FloppyDisk_dyeBlack 305 285 16 16
items/FloppyDisk_dyeBlue 322 285 16 16
items/FloppyDisk_dyeBrown 339 285 16 16
items/FloppyDisk_dyeCyan 356 285 16 16
items/FloppyDisk_dyeGray 373 285 16 16
items/FloppyDisk_dyeGreen 390 285 16 16
items/FloppyDisk_dyeLightBlue 407 285 16 16
items/FloppyDisk_dyeLightGray 424 285 16 16
items/FloppyDisk_dyeLime 441 285 16 16
items/FloppyDisk_dyeMagenta 458 285 16 16
items/FloppyDisk_dyeOrange 475 285 16 16
items/FloppyDisk_dyePink 492 285 16 16
items/FloppyDisk_dyePurple 356 71 16 16
items/FloppyDisk_dyeRed 373 71 16 16
items/FloppyDisk_dyeWhite 390 71 16 16
items/FloppyDisk_dyeYellow 407 71 16 16
items/GraphicsCard0 424 71 16 16
items/GraphicsCard1 441 71 16 16
items/GraphicsCard2 458 71 16 16
items/HardDiskDrive0 475 71 16 16
items/HardDiskDrive1 492 71 16 16
items/HardDiskDrive2 356 88 16 16
items/InternetCard 441 147 16 32
items/LinkedCard 424 122 16 96
items/Memory0 373 88 16 16
items/Memory1 390 88 16 16
items/Memory2 407 88 16 16
items/Memory3 424 88 16 16
items/Memory4 441 88 16 16
items/Memory5 458 88 16 16
items/NetworkCard 475 88 16 16
items/RedstoneCard0 492 88 16 16
items/RedstoneCard1 356 105 16 16
items/Server0 373 105 16 16
items/Server1 390 105 16 16
items/Server2 407 105 16 16
items/Server3 424 105 16 16
items/SoundCard 356 122 16 128
items/WirelessNetworkCard0 441 105 16 16
items/WirelessNetworkCard1 458 105 16 16
light-panel/BookmarkLeft 51 305 18 14
light-panel/BookmarkRight 391 219 20 14
light-panel/BorderB 8 403 4 4
light-panel/BorderL 98 403 4 2
light-panel/BorderR 13 403 4 4
light-panel/BorderT 18 403 4 4
light-panel/CornerBL 23 403 4 4
light-panel/CornerBR 28 403 4 4
light-panel/CornerTL 33 403 4 4
light-panel/CornerTR 38 403 4 4
light-panel/Fill 6 414 2 2
light-panel/Vent 0 355 2 38
nodes/Cable 3 366 8 8
nodes/Camera 475 105 16 16
nodes/Computer 492 105 16 16
nodes/ComputerActivityOverlay 487 0 16 16
nodes/ComputerErrorOverlay 487 17 16 16
nodes/ComputerOnOverlay 487 34 16 16
nodes/DiskDrive 385 51 16 16
nodes/DiskDriveActivity 402 51 16 16
nodes/DiskDriveFloppy 419 51 16 16
nodes/IronNoteBlock 436 51 16 16
nodes/NewNode 453 51 16 16
nodes/NoteBlock 470 51 16 16
nodes/OpenFMRadio 487 51 16 16
nodes/Relay 0 305 16 16
nodes/Screen 17 305 16 16
nodes/ScreenOnOverlay 34 305 16 16
panel/BorderB 43 403 4 4
panel/BorderL 103 403 4 2
panel/BorderR 48 403 4 4
panel/BorderT 53 403 4 4
panel/CornerBL 58 403 4 4
panel/CornerBR 63 403 4 4
panel/CornerTL 68 403 4 4
panel/CornerTR 73 403 4 4
panel/Fill 9 414 2 2
particles/Note 21 355 7 10
screen/BorderB 5 403 2 8
screen/BorderT 2 403 2 10
screen/CornerBL 12 366 8 8
screen/CornerBR 21 366 8 8
screen/CornerTL 3 355 8 10
screen/CornerTR 12 355 8 10
window/BorderDark 2 414 1 4
window/BorderLight 4 414 1 4
window/CloseButton 30 366 7 6
window/CornerBL 78 403 4 4
window/CornerBR 83 403 4 4
window/CornerTL 88 403 4 4
window/CornerTR 93 403 4 4
BarSegment 495 305 16 4
ComputerMotherboard 233 305 79 70
Empty 495 339 1 1
EmptySlot 322 129 18 18
Knob 313 305 50 50
KnobCenter 364 305 50 50
KnobLimits 415 305 50 50
ShadowBorder 491 25 1 24
ShadowCorner 441 0 24 24
TabArrow 441 75 8 14
buttons/BottomDrawerClose 341 129 18 18
buttons/BottomDrawerOpen 360 129 18 18
buttons/OpenFMRadioCloseOff 478 325 7 8
buttons/OpenFMRadioCloseOn 486 325 7 8
buttons/OpenFMRadioRedstoneOff 469 316 8 8
buttons/OpenFMRadioRedstoneOn 478 316 8 8
buttons/OpenFMRadioStartOff 466 0 24 24
buttons/OpenFMRadioStartOn 441 25 24 24
buttons/OpenFMRadioStopOff 466 25 24 24
buttons/OpenFMRadioStopOn 441 50 24 24
buttons/OpenFMRadioVolumeDownOff 450 75 10 10
buttons/OpenFMRadioVolumeDownOn 461 75 10 10
buttons/OpenFMRadioVolumeUpOff 472 75 10 10
buttons/OpenFMRadioVolumeUpOn 483 75 10 10
buttons/PowerOff 379 129 18 18
buttons/PowerOn 398 129 18 18
icons/ButtonCheck 305 162 17 17
icons/ButtonClipboard 323 162 17 17
icons/ButtonRandomize 341 162 17 17
icons/CPU 305 180 16 16
icons/Card 322 180 16 16
icons/ComponentBus 339 180 16 16
icons/DragLMB 417 129 21 14
icons/DragRMB 439 129 21 14
icons/EEPROM 356 180 16 16
icons/Floppy 373 180 16 16
icons/HDD 390 180 16 16
icons/LMB 492 50 11 14
icons/Memory 407 180 16 16
icons/NA 424 180 16 16
icons/NotificationError 473 129 11 11
icons/NotificationInfo 485 129 11 11
icons/NotificationWarning 497 129 11 11
icons/RMB 461 129 11 14
icons/RackMountable 441 180 16 16
icons/SettingsSound 466 50 12 17
icons/SettingsUI 479 50 12 17
icons/Tier0 458 180 16 16
icons/Tier1 475 180 16 16
icons/Tier2 492 180 16 16
icons/WaveLFSR 392 282 24 10
icons/WaveNoise 417 282 24 10
icons/WaveSawtooth 442 282 24 10
icons/WaveSine 467 282 24 10
icons/WaveSquare 380 162 24 10
icons/WaveTriangle 405 162 24 10
icons/WireArrowLeft 493 25 4 8
icons/WireArrowRight 498 25 4 8
items/APU0 373 0 16 96
items/APU1 390 0 16 96
items/APU2 407 0 16 96
items/CPU0 305 197 16 16
items/CPU1 322 197 16 16
items/CPU2 339 197 16 16
items/CardBase 356 197 16 16
items/CircuitBoard 373 197 16 16
items/ComponentBus0 390 197 16 16
items/ComponentBus1 407 197 16 16
items/ComponentBus2 424 197 16 16
items/ComponentBus3 441 197 16 16
items/DataCard0 305 0 16 128
items/DataCard1 322 0 16 128
items/DataCard2 339 0 16 128
items/DebugCard 458 197 16 16
items/DiskDriveMountable 475 197 16 16
items/EEPROM 492 197 16 16
items/FloppyDisk_dyeBlack 305 214 16 16
items/FloppyDisk_dyeBlue 322 214 16 16
items/FloppyDisk_dyeBrown 339 214 16 16
items/FloppyDisk_dyeCyan 356 214 16 16
items/FloppyDisk_dyeGray 373 214 16 16
items/FloppyDisk_dyeGreen 390 214 16 16
items/FloppyDisk_dyeLightBlue 407 214 16 16
items/FloppyDisk_dyeLightGray 424 214 16 16
items/FloppyDisk_dyeLime 441 214 16 16
items/FloppyDisk_dyeMagenta 458 214 16 16
items/FloppyDisk_dyeOrange 475 214 16 16
items/FloppyDisk_dyePink 492 214 16 16
items/FloppyDisk_dyePurple 305 231 16 16
items/FloppyDisk_dyeRed 322 231 16 16
items/FloppyDisk_dyeWhite 339 231 16 16
items/FloppyDisk_dyeYellow 356 231 16 16
items/GraphicsCard0 373 231 16 16
items/GraphicsCard1 390 231 16 16
items/GraphicsCard2 407 231 16 16
items/HardDiskDrive0 424 231 16 16
items/HardDiskDrive1 441 231 16 16
items/HardDiskDrive2 458 231 16 16
items/InternetCard 305 129 16 32
items/LinkedCard 424 0 16 96
items/Memory0 475 231 16 16
items/Memory1 492 231 16 16
items/Memory2 305 248 16 16
items/Memory3 322 248 16 16
items/Memory4 339 248 16 16
items/Memory5 356 248 16 16
items/NetworkCard 373 248 16 16
items/RedstoneCard0 390 248 16 16
items/RedstoneCard1 407 248 16 16
items/Server0 424 248 16 16
items/Server1 441 248 16 16
items/Server2 458 248 16 16
items/Server3 475 248 16 16
items/SoundCard 356 0 16 128
items/WirelessNetworkCard0 492 248 16 16
items/WirelessNetworkCard1 305 265 16 16
light-panel/BookmarkLeft 373 282 18 14
light-panel/BookmarkRight 359 162 20 14
light-panel/BorderB 499 34 4 4
light-panel/BorderL 479 339 4 2
light-panel/BorderR 504 34 4 4
light-panel/BorderT 493 45 4 4
light-panel/CornerBL 498 45 4 4
light-panel/CornerBR 503 45 4 4
light-panel/CornerTL 503 25 4 4
light-panel/CornerTR 502 325 4 4
light-panel/Fill 489 339 2 2
light-panel/Vent 466 305 2 38
nodes/Cable 487 316 8 8
nodes/Camera 322 265 16 16
nodes/Computer 339 265 16 16
nodes/ComputerActivityOverlay 356 265 16 16
nodes/ComputerErrorOverlay 373 265 16 16
nodes/ComputerOnOverlay 390 265 16 16
nodes/DiskDrive 407 265 16 16
nodes/DiskDriveActivity 424 265 16 16
nodes/DiskDriveFloppy 441 265 16 16
nodes/IronNoteBlock 458 265 16 16
nodes/NewNode 475 265 16 16
nodes/NoteBlock 492 265 16 16
nodes/OpenFMRadio 305 282 16 16
nodes/Relay 322 282 16 16
nodes/Screen 339 282 16 16
nodes/ScreenOnOverlay 356 282 16 16
panel/BorderB 507 325 4 4
panel/BorderL 484 339 4 2
panel/BorderR 469 334 4 4
panel/BorderT 474 334 4 4
panel/CornerBL 479 334 4 4
panel/CornerBR 484 334 4 4
panel/CornerTL 489 334 4 4
panel/CornerTR 494 334 4 4
panel/Fill 492 339 2 2
particles/Note 487 305 7 10
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 org.lwjgl.BufferUtils
import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context}
import totoro.ocelot.brain.entity.traits.{DeviceInfo, Entity, Environment}
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.workspace.Workspace
import java.io.BufferedInputStream
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 {
override val node: Component =
@ -20,52 +26,118 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
private val defaultScreenText = "OpenFM"
private val defaultScreenColor = IntColor(0x0AFF0A)
var url: Option[String] = None
var screenColor: IntColor = defaultScreenColor
var screenText: String = defaultScreenText
private var player: Option[StreamAudioPlayer] = None
private var playerThread: Option[Thread] = None
private var thread: Option[Thread] = None
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)
return false
if (isPlaying)
return true
this.playerThread = Option(new Thread(() => {
try {
val connection = new URI(url.get).toURL.openConnection.asInstanceOf[HttpURLConnection]
connection.setDoInput(true)
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()
this.synchronized {
thread = Option(new Thread(() => playSynchronously()))
thread.get.setPriority(Thread.MIN_PRIORITY)
thread.get.start()
}
true
}
def stop(): Boolean = {
if (player.isDefined) {
player.get.stop()
player = None
playerThread = None
this.synchronized {
if (thread.isDefined)
thread.get.interrupt()
}
true
@ -76,17 +148,12 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
def volume_=(value: Float): Unit = {
_volume = value
if (player.isDefined)
player.get.volume = value
if (soundSource.isDefined)
soundSource.get.volume = value
}
private def isPlaying: Boolean = {
if (player.isDefined)
player.get.isPlaying
else if (playerThread.isDefined)
playerThread.get.isAlive
else
false
def isPlaying: Boolean = {
thread.isDefined
}
override def dispose(): Unit = {
@ -153,7 +220,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
@Callback()
def start(context: Context, args: Arguments): Array[AnyRef] = {
result(start())
result(play())
}
@Callback()
@ -221,7 +288,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo {
// Playing again if previously saved value was true
if (nbt.hasKey("isPlaying") && nbt.getBoolean("isPlaying"))
start()
play()
}
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 totoro.ocelot.brain.entity.traits.GenericCamera
import totoro.ocelot.brain.entity.{Cable, Case, FloppyDiskDrive, IronNoteBlock, NoteBlock, Relay, Screen}
import ocelot.desktop.entity.Camera
import ocelot.desktop.entity.openfmradio.OpenFMRadio
import ocelot.desktop.entity.{Camera, OpenFMRadio}
import scala.collection.mutable

View File

@ -2,9 +2,10 @@ package ocelot.desktop.node.nodes
import ocelot.desktop.OcelotDesktop
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.node.Node
import ocelot.desktop.windows.{CameraWindow, OpenFMRadioWindow}
class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends Node(openFMRadio) {
override def icon: String = "nodes/OpenFMRadio"
@ -73,4 +74,11 @@ class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends Node(openFMRadio) {
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()
else onHoverLeave()
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
if (!isSwitch || !isOn) press() else release()
clickSoundSource.play()
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) => {
if (isSwitch) {
if (isOn) {
release()
}
else {
press()
}
}
else {
press()
}
case ClickEvent(MouseEvent.Button.Left, _) =>
if (!isSwitch || !isOn) release() else press()
clickSoundSource.play()
}
case ClickEvent(MouseEvent.Button.Left, _) => {
if (!isSwitch)
release()
}
}
def isOn: Boolean = _isOn
@ -56,20 +70,29 @@ class IconButton(
if (labelTooltip.isDefined)
root.get.tooltipPool.closeTooltip(labelTooltip.get)
def press(): Unit = {
def playPressAnimation(): Unit = {
colorAnimation.goto(pressedColor)
alphaAnimation.goto(1f)
_isOn = true
}
def playReleaseAnimation(): Unit = {
colorAnimation.goto(releasedColor)
alphaAnimation.goto(0f)
_isOn = false
}
def press(): Unit = {
playPressAnimation()
onPressed()
}
def release(): Unit = {
colorAnimation.goto(releasedColor)
alphaAnimation.goto(0f)
_isOn = false
playReleaseAnimation()
onReleased()
}
size = minimumSize
private def releasedIconSize: Size2D = Spritesheet.spriteSize(releasedIcon) * sizeMultiplier
@ -100,8 +123,8 @@ class IconButton(
super.update()
colorAnimation.update()
alphaAnimation.update()
if (isOn && !_isOn) press()
if (!isOn && _isOn) release()
if (isOn && !_isOn) playPressAnimation()
if (!isOn && _isOn) playReleaseAnimation()
}
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 = {
bottomDrawerAnimation.load(nbt, "drawerAnimation")
if (bottomDrawerAnimation.isGoingUp) drawerButton.press()
if (bottomDrawerAnimation.isGoingUp) drawerButton.playPressAnimation()
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)
}
}