mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Add sound card
This commit is contained in:
parent
e5b2e5c37c
commit
9643dc05c1
@ -1 +1 @@
|
||||
Subproject commit e7b477cf4d369d91132275237e72c80ad5440e40
|
||||
Subproject commit 7b2f4ce827d79a7c231bf70c76c28469b705bb9e
|
||||
BIN
sprites/items/SoundCard.png
Normal file
BIN
sprites/items/SoundCard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 899 B |
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 67 KiB |
@ -1,113 +1,114 @@
|
||||
BackgroundPattern 0 0 304 304
|
||||
BarSegment 493 105 16 4
|
||||
BarSegment 488 51 16 4
|
||||
ComputerMotherboard 305 0 79 70
|
||||
Empty 9 316 1 1
|
||||
EmptySlot 441 147 18 18
|
||||
EmptySlot 458 147 18 18
|
||||
Knob 385 0 50 50
|
||||
KnobCenter 436 0 50 50
|
||||
KnobLimits 305 71 50 50
|
||||
ShadowBorder 0 305 1 24
|
||||
ShadowCorner 424 122 24 24
|
||||
TabArrow 458 105 8 14
|
||||
buttons/BottomDrawerClose 460 147 18 18
|
||||
buttons/BottomDrawerOpen 479 147 18 18
|
||||
buttons/PowerOff 424 180 18 18
|
||||
buttons/PowerOn 443 180 18 18
|
||||
icons/ButtonCheck 424 199 17 17
|
||||
icons/ButtonClipboard 442 199 17 17
|
||||
icons/ButtonRandomize 460 199 17 17
|
||||
icons/CPU 478 199 16 16
|
||||
icons/Card 495 199 16 16
|
||||
icons/ComponentBus 449 122 16 16
|
||||
icons/DragLMB 462 180 21 14
|
||||
icons/DragRMB 484 180 21 14
|
||||
icons/EEPROM 466 122 16 16
|
||||
icons/Floppy 483 122 16 16
|
||||
icons/HDD 356 219 16 16
|
||||
icons/LMB 500 0 11 14
|
||||
icons/Memory 373 219 16 16
|
||||
icons/NA 390 219 16 16
|
||||
icons/NotificationError 499 18 11 11
|
||||
icons/NotificationInfo 487 33 11 11
|
||||
icons/NotificationWarning 499 33 11 11
|
||||
icons/RMB 487 18 11 14
|
||||
icons/RackMountable 407 219 16 16
|
||||
icons/SettingsSound 498 147 12 17
|
||||
icons/SettingsUI 487 0 12 17
|
||||
icons/Tier0 424 219 16 16
|
||||
icons/Tier1 441 219 16 16
|
||||
icons/Tier2 458 219 16 16
|
||||
items/APU0 356 122 16 96
|
||||
items/APU1 373 122 16 96
|
||||
items/APU2 390 122 16 96
|
||||
items/CPU0 475 219 16 16
|
||||
items/CPU1 492 219 16 16
|
||||
items/CPU2 305 251 16 16
|
||||
items/CardBase 322 251 16 16
|
||||
items/CircuitBoard 339 251 16 16
|
||||
items/ComponentBus0 356 251 16 16
|
||||
items/ComponentBus1 373 251 16 16
|
||||
items/ComponentBus2 390 251 16 16
|
||||
items/ComponentBus3 407 251 16 16
|
||||
ShadowCorner 441 122 24 24
|
||||
TabArrow 502 122 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 391 219 16 16
|
||||
icons/Card 408 219 16 16
|
||||
icons/ComponentBus 425 219 16 16
|
||||
icons/DragLMB 441 199 21 14
|
||||
icons/DragRMB 463 199 21 14
|
||||
icons/EEPROM 442 219 16 16
|
||||
icons/Floppy 459 219 16 16
|
||||
icons/HDD 476 219 16 16
|
||||
icons/LMB 402 51 11 14
|
||||
icons/Memory 493 219 16 16
|
||||
icons/NA 305 251 16 16
|
||||
icons/NotificationError 426 51 11 11
|
||||
icons/NotificationInfo 438 51 11 11
|
||||
icons/NotificationWarning 450 51 11 11
|
||||
icons/RMB 414 51 11 14
|
||||
icons/RackMountable 322 251 16 16
|
||||
icons/SettingsSound 498 180 12 17
|
||||
icons/SettingsUI 496 147 12 17
|
||||
icons/Tier0 339 251 16 16
|
||||
icons/Tier1 356 251 16 16
|
||||
icons/Tier2 373 251 16 16
|
||||
items/APU0 373 122 16 96
|
||||
items/APU1 390 122 16 96
|
||||
items/APU2 407 122 16 96
|
||||
items/CPU0 390 251 16 16
|
||||
items/CPU1 407 251 16 16
|
||||
items/CPU2 424 251 16 16
|
||||
items/CardBase 441 251 16 16
|
||||
items/CircuitBoard 458 251 16 16
|
||||
items/ComponentBus0 475 251 16 16
|
||||
items/ComponentBus1 492 251 16 16
|
||||
items/ComponentBus2 305 268 16 16
|
||||
items/ComponentBus3 322 268 16 16
|
||||
items/DataCard0 305 122 16 128
|
||||
items/DataCard1 322 122 16 128
|
||||
items/DataCard2 339 122 16 128
|
||||
items/DebugCard 424 251 16 16
|
||||
items/DiskDriveMountable 441 251 16 16
|
||||
items/EEPROM 458 251 16 16
|
||||
items/FloppyDisk_dyeBlack 475 251 16 16
|
||||
items/FloppyDisk_dyeBlue 492 251 16 16
|
||||
items/FloppyDisk_dyeBrown 305 268 16 16
|
||||
items/FloppyDisk_dyeCyan 322 268 16 16
|
||||
items/FloppyDisk_dyeGray 339 268 16 16
|
||||
items/FloppyDisk_dyeGreen 356 268 16 16
|
||||
items/FloppyDisk_dyeLightBlue 373 268 16 16
|
||||
items/FloppyDisk_dyeLightGray 390 268 16 16
|
||||
items/FloppyDisk_dyeLime 407 268 16 16
|
||||
items/FloppyDisk_dyeMagenta 424 268 16 16
|
||||
items/FloppyDisk_dyeOrange 441 268 16 16
|
||||
items/FloppyDisk_dyePink 458 268 16 16
|
||||
items/FloppyDisk_dyePurple 475 268 16 16
|
||||
items/FloppyDisk_dyeRed 492 268 16 16
|
||||
items/FloppyDisk_dyeWhite 305 285 16 16
|
||||
items/FloppyDisk_dyeYellow 322 285 16 16
|
||||
items/GraphicsCard0 339 285 16 16
|
||||
items/GraphicsCard1 356 285 16 16
|
||||
items/GraphicsCard2 373 285 16 16
|
||||
items/HardDiskDrive0 390 285 16 16
|
||||
items/HardDiskDrive1 407 285 16 16
|
||||
items/HardDiskDrive2 424 285 16 16
|
||||
items/InternetCard 424 147 16 32
|
||||
items/LinkedCard 407 122 16 96
|
||||
items/Memory0 441 285 16 16
|
||||
items/Memory1 458 285 16 16
|
||||
items/Memory2 475 285 16 16
|
||||
items/Memory3 492 285 16 16
|
||||
items/Memory4 356 71 16 16
|
||||
items/Memory5 373 71 16 16
|
||||
items/NetworkCard 390 71 16 16
|
||||
items/RedstoneCard0 407 71 16 16
|
||||
items/RedstoneCard1 424 71 16 16
|
||||
items/Server0 441 71 16 16
|
||||
items/Server1 458 71 16 16
|
||||
items/Server2 475 71 16 16
|
||||
items/Server3 492 71 16 16
|
||||
items/WirelessNetworkCard0 356 88 16 16
|
||||
items/WirelessNetworkCard1 373 88 16 16
|
||||
nodes/Cable 356 236 8 8
|
||||
nodes/Computer 390 88 16 16
|
||||
nodes/ComputerActivityOverlay 407 88 16 16
|
||||
nodes/ComputerErrorOverlay 424 88 16 16
|
||||
nodes/ComputerOnOverlay 441 88 16 16
|
||||
nodes/DiskDrive 458 88 16 16
|
||||
nodes/DiskDriveActivity 475 88 16 16
|
||||
nodes/DiskDriveFloppy 492 88 16 16
|
||||
nodes/IronNoteBlock 356 105 16 16
|
||||
nodes/NewNode 373 105 16 16
|
||||
nodes/NoteBlock 390 105 16 16
|
||||
nodes/Relay 407 105 16 16
|
||||
nodes/Screen 424 105 16 16
|
||||
nodes/ScreenOnOverlay 441 105 16 16
|
||||
items/DebugCard 339 268 16 16
|
||||
items/DiskDriveMountable 356 268 16 16
|
||||
items/EEPROM 373 268 16 16
|
||||
items/FloppyDisk_dyeBlack 390 268 16 16
|
||||
items/FloppyDisk_dyeBlue 407 268 16 16
|
||||
items/FloppyDisk_dyeBrown 424 268 16 16
|
||||
items/FloppyDisk_dyeCyan 441 268 16 16
|
||||
items/FloppyDisk_dyeGray 458 268 16 16
|
||||
items/FloppyDisk_dyeGreen 475 268 16 16
|
||||
items/FloppyDisk_dyeLightBlue 492 268 16 16
|
||||
items/FloppyDisk_dyeLightGray 305 285 16 16
|
||||
items/FloppyDisk_dyeLime 322 285 16 16
|
||||
items/FloppyDisk_dyeMagenta 339 285 16 16
|
||||
items/FloppyDisk_dyeOrange 356 285 16 16
|
||||
items/FloppyDisk_dyePink 373 285 16 16
|
||||
items/FloppyDisk_dyePurple 390 285 16 16
|
||||
items/FloppyDisk_dyeRed 407 285 16 16
|
||||
items/FloppyDisk_dyeWhite 424 285 16 16
|
||||
items/FloppyDisk_dyeYellow 441 285 16 16
|
||||
items/GraphicsCard0 458 285 16 16
|
||||
items/GraphicsCard1 475 285 16 16
|
||||
items/GraphicsCard2 492 285 16 16
|
||||
items/HardDiskDrive0 356 71 16 16
|
||||
items/HardDiskDrive1 373 71 16 16
|
||||
items/HardDiskDrive2 390 71 16 16
|
||||
items/InternetCard 441 147 16 32
|
||||
items/LinkedCard 424 122 16 96
|
||||
items/Memory0 407 71 16 16
|
||||
items/Memory1 424 71 16 16
|
||||
items/Memory2 441 71 16 16
|
||||
items/Memory3 458 71 16 16
|
||||
items/Memory4 475 71 16 16
|
||||
items/Memory5 492 71 16 16
|
||||
items/NetworkCard 356 88 16 16
|
||||
items/RedstoneCard0 373 88 16 16
|
||||
items/RedstoneCard1 390 88 16 16
|
||||
items/Server0 407 88 16 16
|
||||
items/Server1 424 88 16 16
|
||||
items/Server2 441 88 16 16
|
||||
items/Server3 458 88 16 16
|
||||
items/SoundCard 356 122 16 128
|
||||
items/WirelessNetworkCard0 475 88 16 16
|
||||
items/WirelessNetworkCard1 492 88 16 16
|
||||
nodes/Cable 373 237 8 8
|
||||
nodes/Computer 356 105 16 16
|
||||
nodes/ComputerActivityOverlay 373 105 16 16
|
||||
nodes/ComputerErrorOverlay 390 105 16 16
|
||||
nodes/ComputerOnOverlay 407 105 16 16
|
||||
nodes/DiskDrive 424 105 16 16
|
||||
nodes/DiskDriveActivity 441 105 16 16
|
||||
nodes/DiskDriveFloppy 458 105 16 16
|
||||
nodes/IronNoteBlock 475 105 16 16
|
||||
nodes/NewNode 492 105 16 16
|
||||
nodes/NoteBlock 487 0 16 16
|
||||
nodes/Relay 487 17 16 16
|
||||
nodes/Screen 487 34 16 16
|
||||
nodes/ScreenOnOverlay 385 51 16 16
|
||||
panel/BorderB 8 305 4 4
|
||||
panel/BorderL 63 305 4 2
|
||||
panel/BorderR 13 305 4 4
|
||||
@ -117,16 +118,16 @@ panel/CornerBR 28 305 4 4
|
||||
panel/CornerTL 33 305 4 4
|
||||
panel/CornerTR 38 305 4 4
|
||||
panel/Fill 6 316 2 2
|
||||
particles/Note 485 105 7 10
|
||||
particles/Note 480 51 7 10
|
||||
screen/BorderB 5 305 2 8
|
||||
screen/BorderT 2 305 2 10
|
||||
screen/CornerBL 365 236 8 8
|
||||
screen/CornerBR 374 236 8 8
|
||||
screen/CornerTL 467 105 8 10
|
||||
screen/CornerTR 476 105 8 10
|
||||
screen/CornerBL 382 237 8 8
|
||||
screen/CornerBR 391 237 8 8
|
||||
screen/CornerTL 462 51 8 10
|
||||
screen/CornerTR 471 51 8 10
|
||||
window/BorderDark 2 316 1 4
|
||||
window/BorderLight 4 316 1 4
|
||||
window/CloseButton 383 236 7 6
|
||||
window/CloseButton 400 237 7 6
|
||||
window/CornerBL 43 305 4 4
|
||||
window/CornerBR 48 305 4 4
|
||||
window/CornerTL 53 305 4 4
|
||||
|
||||
89
src/main/scala/ocelot/desktop/audio/AL10W.scala
Normal file
89
src/main/scala/ocelot/desktop/audio/AL10W.scala
Normal file
@ -0,0 +1,89 @@
|
||||
package ocelot.desktop.audio
|
||||
|
||||
import ocelot.desktop.util.Logging
|
||||
import org.lwjgl.openal.AL10
|
||||
|
||||
import java.nio.{ByteBuffer, IntBuffer}
|
||||
|
||||
object AL10W extends Logging {
|
||||
private def run[T](func: String)(f: => T): T = {
|
||||
// logger.debug(func)
|
||||
val res = f
|
||||
val err = AL10.alGetError()
|
||||
if (err != AL10.AL_NO_ERROR) {
|
||||
val errName = classOf[AL10].getDeclaredFields.find(field => {
|
||||
try {
|
||||
field.getInt() == err
|
||||
} catch {
|
||||
case _: Exception => false
|
||||
}
|
||||
}).map(_.getName).getOrElse(err.toHexString)
|
||||
logger.error(s"OpenAL error: ${func}: ${errName}")
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
def alIsExtensionPresent(name: String): Boolean = run("alIsExtensionPresent") {
|
||||
AL10.alIsExtensionPresent(name)
|
||||
}
|
||||
|
||||
def alGenBuffers(): Int = run("alGenBuffers") {
|
||||
AL10.alGenBuffers()
|
||||
}
|
||||
|
||||
def alBufferData(buffer: Int, format: Int, data: ByteBuffer, freq: Int): Unit = run("alBufferData") {
|
||||
AL10.alBufferData(buffer, format, data, freq)
|
||||
}
|
||||
|
||||
def alGetBufferi(buffer: Int, pname: Int): Int = run("alGetBufferi") {
|
||||
AL10.alGetBufferi(buffer, pname)
|
||||
}
|
||||
|
||||
def alDeleteBuffers(buffer: Int): Unit = run("alDeleteBuffers") {
|
||||
AL10.alDeleteBuffers(buffer)
|
||||
}
|
||||
|
||||
def alGenSources(): Int = run("alGenSources") {
|
||||
AL10.alGenSources()
|
||||
}
|
||||
|
||||
def alSourceQueueBuffers(source: Int, buffer: Int): Unit = run("alSourceQueueBuffers") {
|
||||
AL10.alSourceQueueBuffers(source, buffer)
|
||||
}
|
||||
|
||||
def alSourceUnqueueBuffers(source: Int, buffers: IntBuffer): Unit = run("alSourceUnqueueBuffers") {
|
||||
AL10.alSourceUnqueueBuffers(source, buffers)
|
||||
}
|
||||
|
||||
def alGetSourcei(source: Int, pname: Int): Int = run("alGetSourcei") {
|
||||
AL10.alGetSourcei(source, pname)
|
||||
}
|
||||
|
||||
def alSourcei(source: Int, pname: Int, value: Int): Unit = run("alSourcei") {
|
||||
AL10.alSourcei(source, pname, value)
|
||||
}
|
||||
|
||||
def alSourcef(source: Int, pname: Int, value: Float): Unit = run("alSourcef") {
|
||||
AL10.alSourcef(source, pname, value)
|
||||
}
|
||||
|
||||
def alSource3f(source: Int, pname: Int, v1: Float, v2: Float, v3: Float): Unit = run("alSource3f") {
|
||||
AL10.alSource3f(source, pname, v1, v2, v3)
|
||||
}
|
||||
|
||||
def alSourcePlay(source: Int): Unit = run("alSourcePlay") {
|
||||
AL10.alSourcePlay(source)
|
||||
}
|
||||
|
||||
def alSourcePause(source: Int): Unit = run("alSourceStop") {
|
||||
AL10.alSourcePause(source)
|
||||
}
|
||||
|
||||
def alSourceStop(source: Int): Unit = run("alSourceStop") {
|
||||
AL10.alSourceStop(source)
|
||||
}
|
||||
|
||||
def alDeleteSources(source: Int): Unit = run("alDeleteSources") {
|
||||
AL10.alDeleteSources(source)
|
||||
}
|
||||
}
|
||||
@ -5,12 +5,13 @@ import ocelot.desktop.util.Logging
|
||||
import org.lwjgl.LWJGLException
|
||||
import org.lwjgl.openal.{AL, AL10, ALC10}
|
||||
|
||||
import java.nio.{ByteBuffer, ByteOrder}
|
||||
import scala.collection.mutable
|
||||
|
||||
object Audio extends Logging {
|
||||
val sampleRate: Int = 44100
|
||||
|
||||
private val sources = new mutable.HashMap[SoundSource, SourceState]
|
||||
private val sources = new mutable.HashMap[SoundSource, Int]
|
||||
private var _disabled = true
|
||||
|
||||
/**
|
||||
@ -19,7 +20,6 @@ object Audio extends Logging {
|
||||
def init(): Unit = {
|
||||
try {
|
||||
AL.create()
|
||||
AL10.alGetError()
|
||||
logger.info(s"OpenAL device: ${ALC10.alcGetString(AL.getDevice, ALC10.ALC_DEVICE_SPECIFIER)}")
|
||||
_disabled = false
|
||||
} catch {
|
||||
@ -31,16 +31,49 @@ object Audio extends Logging {
|
||||
|
||||
def isDisabled: Boolean = _disabled
|
||||
|
||||
def numSources: Int = synchronized { sources.size }
|
||||
def numSources: Int = synchronized {
|
||||
sources.size
|
||||
}
|
||||
|
||||
def newStream(soundCategory: SoundCategory.Value, pitch: Float = 1f,
|
||||
volume: Float = 1f): (SoundStream, SoundSource) =
|
||||
{
|
||||
var source: SoundSource = null
|
||||
|
||||
val stream = new SoundStream {
|
||||
override def enqueue(samples: SoundSamples): Unit = Audio.synchronized {
|
||||
val sourceId = if (sources.contains(source)) {
|
||||
sources(source)
|
||||
} else {
|
||||
val sourceId = AL10W.alGenSources()
|
||||
|
||||
AL10W.alSourcef(sourceId, AL10.AL_PITCH, source.pitch)
|
||||
AL10W.alSource3f(sourceId, AL10.AL_POSITION, 0f, 0f, 0f)
|
||||
AL10W.alSourcef(sourceId, AL10.AL_GAIN, source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster)
|
||||
sources.put(source, sourceId)
|
||||
|
||||
sourceId
|
||||
}
|
||||
|
||||
cleanupSourceBuffers(sourceId)
|
||||
|
||||
val bufferId = samples.genBuffer()
|
||||
AL10W.alSourceQueueBuffers(sourceId, bufferId)
|
||||
if (AL10W.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING)
|
||||
AL10W.alSourcePlay(sourceId)
|
||||
}
|
||||
}
|
||||
|
||||
source = SoundSource.fromStream(stream, soundCategory, looping = false, pitch, volume)
|
||||
(stream, source)
|
||||
}
|
||||
|
||||
def getSourceStatus(source: SoundSource): SoundSource.Status.Value = synchronized {
|
||||
if (!sources.contains(source))
|
||||
return SoundSource.Status.Stopped
|
||||
|
||||
sources(source).status
|
||||
|
||||
val state = sources(source)
|
||||
AL10.alGetSourcei(state.sourceId, AL10.AL_SOURCE_STATE) match {
|
||||
val sourceId = sources(source)
|
||||
AL10W.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) match {
|
||||
case AL10.AL_PLAYING => SoundSource.Status.Playing
|
||||
case AL10.AL_PAUSED => SoundSource.Status.Paused
|
||||
case _ => SoundSource.Status.Stopped
|
||||
@ -48,75 +81,63 @@ object Audio extends Logging {
|
||||
}
|
||||
|
||||
def playSource(source: SoundSource): Unit = synchronized {
|
||||
if (getSourceStatus(source) == SoundSource.Status.Playing)
|
||||
return
|
||||
|
||||
if (sources.contains(source)) {
|
||||
AL10.alSourcePlay(sources(source).sourceId)
|
||||
AL10W.alSourcePlay(sources(source))
|
||||
return
|
||||
}
|
||||
|
||||
val sourceId = AL10.alGenSources()
|
||||
val (bufferId, isAssociated) = source.kind match {
|
||||
case SoundSource.KindSoundBuffer(buffer) => (buffer.bufferId, false)
|
||||
case SoundSource.KindSoundSamples(SoundSamples(buffer, rate, channels)) =>
|
||||
val bufferId = AL10.alGenBuffers()
|
||||
val format = channels match {
|
||||
case 2 => AL10.AL_FORMAT_STEREO16
|
||||
case 1 => AL10.AL_FORMAT_MONO16
|
||||
}
|
||||
|
||||
AL10.alBufferData(bufferId, format, buffer, rate)
|
||||
val err = AL10.alGetError
|
||||
if (err != AL10.AL_NO_ERROR) {
|
||||
logger.error(s"Unable to create OpenAL buffer: $err, $format, $rate")
|
||||
return
|
||||
}
|
||||
|
||||
(bufferId, true)
|
||||
val sourceId = AL10W.alGenSources()
|
||||
source.kind match {
|
||||
case SoundSource.KindSoundBuffer(buffer) =>
|
||||
AL10W.alSourcei(sourceId, AL10.AL_BUFFER, buffer.bufferId)
|
||||
case SoundSource.KindSoundSamples(samples) =>
|
||||
val bufferId = samples.genBuffer()
|
||||
if (bufferId != -1) AL10W.alSourceQueueBuffers(sourceId, bufferId)
|
||||
case SoundSource.KindStream(_) =>
|
||||
}
|
||||
|
||||
AL10.alSourcei(sourceId, AL10.AL_BUFFER, bufferId)
|
||||
AL10.alSourcef(sourceId, AL10.AL_PITCH, source.pitch)
|
||||
AL10.alSource3f(sourceId, AL10.AL_POSITION, 0f, 0f, 0f)
|
||||
AL10.alSourcei(sourceId, AL10.AL_LOOPING, if (source.looping) AL10.AL_TRUE else AL10.AL_FALSE)
|
||||
AL10.alSourcef(sourceId, AL10.AL_GAIN, source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster)
|
||||
AL10.alSourcePlay(sourceId)
|
||||
AL10W.alSourcef(sourceId, AL10.AL_PITCH, source.pitch)
|
||||
AL10W.alSource3f(sourceId, AL10.AL_POSITION, 0f, 0f, 0f)
|
||||
AL10W.alSourcei(sourceId, AL10.AL_LOOPING, if (source.looping) AL10.AL_TRUE else AL10.AL_FALSE)
|
||||
AL10W.alSourcef(sourceId, AL10.AL_GAIN, source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster)
|
||||
AL10W.alSourcePlay(sourceId)
|
||||
|
||||
val state = SourceState(sourceId, associatedBufferId = if (isAssociated) bufferId else -1, SoundSource.Status.Playing)
|
||||
sources.put(source, state)
|
||||
sources.put(source, sourceId)
|
||||
}
|
||||
|
||||
def pauseSource(source: SoundSource): Unit = synchronized {
|
||||
if (getSourceStatus(source) == SoundSource.Status.Paused)
|
||||
return
|
||||
|
||||
if (sources.contains(source)) {
|
||||
AL10.alSourcePause(sources(source).sourceId)
|
||||
AL10W.alSourcePause(sources(source))
|
||||
}
|
||||
}
|
||||
|
||||
def stopSource(source: SoundSource): Unit = synchronized {
|
||||
if (getSourceStatus(source) == SoundSource.Status.Stopped)
|
||||
return
|
||||
|
||||
if (sources.contains(source)) {
|
||||
val state = sources(source)
|
||||
AL10.alSourceStop(state.sourceId)
|
||||
AL10.alDeleteSources(state.sourceId)
|
||||
if (state.associatedBufferId != -1)
|
||||
AL10.alDeleteBuffers(state.associatedBufferId)
|
||||
AL10W.alSourceStop(sources(source))
|
||||
}
|
||||
}
|
||||
|
||||
def update(): Unit = synchronized {
|
||||
if (isDisabled) return
|
||||
|
||||
sources.filterInPlace { case (source, state) =>
|
||||
AL10.alSourcef(state.sourceId, AL10.AL_GAIN, source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster)
|
||||
AL10.alGetSourcei(state.sourceId, AL10.AL_SOURCE_STATE) match {
|
||||
case AL10.AL_PLAYING =>
|
||||
state.status = SoundSource.Status.Playing
|
||||
true
|
||||
case AL10.AL_PAUSED =>
|
||||
state.status = SoundSource.Status.Paused
|
||||
true
|
||||
case _ =>
|
||||
AL10.alDeleteSources(state.sourceId)
|
||||
if (state.associatedBufferId != -1)
|
||||
AL10.alDeleteBuffers(state.associatedBufferId)
|
||||
sources.filterInPlace { case (source, sourceId) =>
|
||||
cleanupSourceBuffers(sourceId)
|
||||
|
||||
AL10W.alSourcef(sourceId, AL10.AL_GAIN, source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster)
|
||||
AL10W.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) match {
|
||||
case AL10.AL_STOPPED =>
|
||||
deleteSource(sourceId)
|
||||
false
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,15 +145,32 @@ object Audio extends Logging {
|
||||
def destroy(): Unit = synchronized {
|
||||
if (isDisabled) return
|
||||
|
||||
for ((_, state) <- sources) {
|
||||
AL10.alDeleteSources(state.sourceId)
|
||||
if (state.associatedBufferId != -1)
|
||||
AL10.alDeleteBuffers(state.associatedBufferId)
|
||||
for ((_, sourceId) <- sources) {
|
||||
deleteSource(sourceId)
|
||||
}
|
||||
|
||||
sources.clear()
|
||||
_disabled = true
|
||||
}
|
||||
|
||||
private case class SourceState(sourceId: Int, associatedBufferId: Int, var status: SoundSource.Status.Value)
|
||||
private def deleteSource(sourceId: Int): Unit = {
|
||||
AL10W.alSourceStop(sourceId)
|
||||
cleanupSourceBuffers(sourceId)
|
||||
AL10W.alDeleteSources(sourceId)
|
||||
}
|
||||
|
||||
private def cleanupSourceBuffers(sourceId: Int): Unit = {
|
||||
val count = AL10W.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED)
|
||||
if (count <= 0) return
|
||||
|
||||
val buff = ByteBuffer.allocateDirect(count * 4)
|
||||
buff.order(ByteOrder.nativeOrder())
|
||||
val buf = buff.asIntBuffer()
|
||||
|
||||
AL10W.alSourceUnqueueBuffers(sourceId, buf)
|
||||
|
||||
for (i <- 0 until count) {
|
||||
AL10W.alDeleteBuffers(buf.get(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,6 @@ object BeepGenerator {
|
||||
}
|
||||
|
||||
data.rewind()
|
||||
SoundSource.fromSamples(SoundSamples(data, Audio.sampleRate, 1), SoundCategory.Beep)
|
||||
SoundSource.fromSamples(SoundSamples(data, Audio.sampleRate, SoundSamples.Format.Mono16), SoundCategory.Beep)
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,11 @@ object OggDecoder {
|
||||
data.put(dataOut.toByteArray)
|
||||
data.rewind()
|
||||
|
||||
SoundSamples(data, rate, channels)
|
||||
val format = channels match {
|
||||
case 1 => SoundSamples.Format.Mono16
|
||||
case 2 => SoundSamples.Format.Stereo16
|
||||
}
|
||||
|
||||
SoundSamples(data, rate, format)
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,9 +13,9 @@ class SoundBuffer(val file: String) extends Resource with Logging {
|
||||
return
|
||||
|
||||
logger.debug(s"Loading sound buffer from '$file'...")
|
||||
_bufferId = AL10.alGenBuffers()
|
||||
_bufferId = AL10W.alGenBuffers()
|
||||
|
||||
if (AL10.alIsExtensionPresent("AL_EXT_vorbis")) {
|
||||
if (AL10W.alIsExtensionPresent("AL_EXT_vorbis")) {
|
||||
initWithExt()
|
||||
} else {
|
||||
initFallback()
|
||||
@ -29,21 +29,12 @@ class SoundBuffer(val file: String) extends Resource with Logging {
|
||||
return
|
||||
}
|
||||
|
||||
AL10.alBufferData(bufferId, AL10.AL_FORMAT_VORBIS_EXT, fileBuffer, -1)
|
||||
AL10W.alBufferData(bufferId, AL10.AL_FORMAT_VORBIS_EXT, fileBuffer, -1)
|
||||
}
|
||||
|
||||
private def initFallback(): Unit = {
|
||||
val ogg = OggDecoder.decode(getClass.getResourceAsStream(file))
|
||||
|
||||
val format = ogg.channels match {
|
||||
case 2 => AL10.AL_FORMAT_STEREO16
|
||||
case 1 => AL10.AL_FORMAT_MONO16
|
||||
}
|
||||
|
||||
AL10.alBufferData(bufferId, format, ogg.data, ogg.rate)
|
||||
if (AL10.alGetError != AL10.AL_NO_ERROR) {
|
||||
logger.error(s"Unable to create OpenAL buffer from '$file'!")
|
||||
}
|
||||
_bufferId = ogg.genBuffer()
|
||||
}
|
||||
|
||||
def numSamples: Int = {
|
||||
@ -51,9 +42,9 @@ class SoundBuffer(val file: String) extends Resource with Logging {
|
||||
return 0
|
||||
}
|
||||
|
||||
val sizeBytes = AL10.alGetBufferi(bufferId, AL10.AL_SIZE)
|
||||
val channels = AL10.alGetBufferi(bufferId, AL10.AL_CHANNELS)
|
||||
val bits = AL10.alGetBufferi(bufferId, AL10.AL_BITS)
|
||||
val sizeBytes = AL10W.alGetBufferi(bufferId, AL10.AL_SIZE)
|
||||
val channels = AL10W.alGetBufferi(bufferId, AL10.AL_CHANNELS)
|
||||
val bits = AL10W.alGetBufferi(bufferId, AL10.AL_BITS)
|
||||
|
||||
sizeBytes * 8 / channels / bits
|
||||
}
|
||||
@ -62,7 +53,7 @@ class SoundBuffer(val file: String) extends Resource with Logging {
|
||||
if (bufferId == -1) {
|
||||
44100
|
||||
} else {
|
||||
AL10.alGetBufferi(bufferId, AL10.AL_FREQUENCY)
|
||||
AL10W.alGetBufferi(bufferId, AL10.AL_FREQUENCY)
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +61,7 @@ class SoundBuffer(val file: String) extends Resource with Logging {
|
||||
|
||||
def freeResource(): Unit = {
|
||||
if (bufferId != -1) {
|
||||
AL10.alDeleteBuffers(bufferId)
|
||||
AL10W.alDeleteBuffers(bufferId)
|
||||
logger.debug(s"Destroyed sound buffer (ID: $bufferId) loaded from $file")
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,28 @@
|
||||
package ocelot.desktop.audio
|
||||
|
||||
import ocelot.desktop.util.Logging
|
||||
import org.lwjgl.openal.AL10
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
case class SoundSamples(data: ByteBuffer, rate: Int, channels: Int)
|
||||
case class SoundSamples(data: ByteBuffer, rate: Int, format: SoundSamples.Format.Value) extends Logging {
|
||||
def genBuffer(): Int = {
|
||||
val bufferId = AL10W.alGenBuffers()
|
||||
val formatId = format match {
|
||||
case SoundSamples.Format.Stereo16 => AL10.AL_FORMAT_STEREO16
|
||||
case SoundSamples.Format.Mono16 => AL10.AL_FORMAT_MONO16
|
||||
case SoundSamples.Format.Mono8 => AL10.AL_FORMAT_MONO8
|
||||
}
|
||||
|
||||
AL10W.alBufferData(bufferId, formatId, data, rate)
|
||||
bufferId
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
object SoundSamples {
|
||||
object Format extends Enumeration {
|
||||
val Stereo16, Mono8, Mono16 = Value
|
||||
}
|
||||
}
|
||||
@ -12,8 +12,13 @@ class SoundSource(val kind: SoundSource.Kind,
|
||||
val seconds = kind match {
|
||||
case SoundSource.KindSoundBuffer(buffer) =>
|
||||
buffer.numSamples.toFloat / buffer.sampleRate
|
||||
case SoundSource.KindSoundSamples(SoundSamples(buffer, rate, channels)) =>
|
||||
buffer.limit().toFloat / (rate * channels)
|
||||
case SoundSource.KindSoundSamples(SoundSamples(buffer, rate, format)) =>
|
||||
val bps = format match {
|
||||
case SoundSamples.Format.Stereo16 => 2
|
||||
case SoundSamples.Format.Mono8 => 1
|
||||
case SoundSamples.Format.Mono16 => 2
|
||||
}
|
||||
buffer.limit().toFloat / (rate * bps)
|
||||
}
|
||||
|
||||
Duration.ofNanos((seconds * 1e9).toLong)
|
||||
@ -36,15 +41,15 @@ class SoundSource(val kind: SoundSource.Kind,
|
||||
}
|
||||
|
||||
def play(): Unit = {
|
||||
if (!isPlaying) Audio.playSource(this)
|
||||
Audio.playSource(this)
|
||||
}
|
||||
|
||||
def pause(): Unit = {
|
||||
if (!isPaused) Audio.pauseSource(this)
|
||||
Audio.pauseSource(this)
|
||||
}
|
||||
|
||||
def stop(): Unit = {
|
||||
if (!isStopped) Audio.stopSource(this)
|
||||
Audio.stopSource(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +60,8 @@ object SoundSource {
|
||||
|
||||
case class KindSoundSamples(samples: SoundSamples) extends Kind
|
||||
|
||||
case class KindStream(stream: SoundStream) extends Kind
|
||||
|
||||
def fromBuffer(buffer: SoundBuffer, soundCategory: SoundCategory.Value,
|
||||
looping: Boolean = false, pitch: Float = 1f, volume: Float = 1f): SoundSource = {
|
||||
new SoundSource(SoundSource.KindSoundBuffer(buffer), soundCategory, looping, pitch, volume)
|
||||
@ -65,6 +72,12 @@ object SoundSource {
|
||||
new SoundSource(SoundSource.KindSoundSamples(samples), soundCategory, looping, pitch, volume)
|
||||
}
|
||||
|
||||
def fromStream(stream: SoundStream, soundCategory: SoundCategory.Value,
|
||||
looping: Boolean = false, pitch: Float = 1f, volume: Float = 1f): SoundSource =
|
||||
{
|
||||
new SoundSource(SoundSource.KindStream(stream), soundCategory, looping, pitch, volume)
|
||||
}
|
||||
|
||||
object Status extends Enumeration {
|
||||
val Playing, Paused, Stopped = Value
|
||||
}
|
||||
|
||||
5
src/main/scala/ocelot/desktop/audio/SoundStream.scala
Normal file
5
src/main/scala/ocelot/desktop/audio/SoundStream.scala
Normal file
@ -0,0 +1,5 @@
|
||||
package ocelot.desktop.audio
|
||||
|
||||
trait SoundStream {
|
||||
def enqueue(samples: SoundSamples): Unit
|
||||
}
|
||||
@ -13,10 +13,11 @@ import org.lwjgl.input.Keyboard
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Environment, Floppy, GenericCPU, Inventory}
|
||||
import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, HDDUnmanaged, Memory}
|
||||
import totoro.ocelot.brain.event.FileSystemActivityType.Floppy
|
||||
import totoro.ocelot.brain.event.{BeepEvent, BeepPatternEvent, FileSystemActivityEvent, MachineCrashEvent}
|
||||
import totoro.ocelot.brain.event.{BeepEvent, BeepPatternEvent, FileSystemActivityEvent, MachineCrashEvent, SoundCardAudioEvent}
|
||||
import totoro.ocelot.brain.loot.Loot
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import scala.reflect.ClassTag
|
||||
import scala.util.Random
|
||||
|
||||
@ -29,6 +30,7 @@ class ComputerNode(val computer: Case) extends Node(computer) with Logging {
|
||||
var floppySlot: Option[FloppySlot] = None
|
||||
|
||||
private val soundComputerRunning = SoundSource.fromBuffer(SoundBuffers.MachineComputerRunning, SoundCategory.Environment, looping = true)
|
||||
private var soundCardStream: SoundStream = _
|
||||
|
||||
setupSlots()
|
||||
refitSlots()
|
||||
@ -48,6 +50,17 @@ class ComputerNode(val computer: Case) extends Node(computer) with Logging {
|
||||
val soundHDDAccess = SoundBuffers.MachineHDDAccess.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
|
||||
val sound = if (event.activityType == Floppy) soundFloppyAccess else soundHDDAccess
|
||||
sound(Random.between(0, sound.length)).play()
|
||||
|
||||
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
|
||||
val samples = SoundSamples(event.data, 44100, SoundSamples.Format.Mono8)
|
||||
// SoundSource.fromSamples(samples, SoundCategory.Beep).play()
|
||||
if (soundCardStream == null) {
|
||||
val (stream, source) = Audio.newStream(SoundCategory.Beep)
|
||||
soundCardStream = stream
|
||||
soundCardStream.enqueue(samples)
|
||||
} else {
|
||||
soundCardStream.enqueue(samples)
|
||||
}
|
||||
}
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean = super.shouldReceiveEventsFor(address) ||
|
||||
|
||||
@ -249,11 +249,10 @@ object UiHandler extends Logging {
|
||||
if (Display.isCloseRequested)
|
||||
OcelotDesktop.exit()
|
||||
|
||||
Audio.update()
|
||||
|
||||
OcelotDesktop.withTickLockAcquired {
|
||||
updateWindowSizeAndPosition()
|
||||
|
||||
Audio.update()
|
||||
KeyEvents.update()
|
||||
MouseEvents.update()
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package ocelot.desktop.ui.widget.slot
|
||||
|
||||
import ocelot.desktop.graphics.IconDef
|
||||
import totoro.ocelot.brain.entity.sound_card.SoundCard
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Tiered}
|
||||
import totoro.ocelot.brain.entity.{DataCard, GraphicsCard, InternetCard, LinkedCard, NetworkCard, Redstone, WirelessNetworkCard}
|
||||
|
||||
@ -15,7 +16,7 @@ object CardRegistry {
|
||||
val iconByClassAndTier: mutable.HashMap[(String, Int), IconDef] = mutable.HashMap()
|
||||
val entries: mutable.ArrayBuffer[Entry] = mutable.ArrayBuffer()
|
||||
|
||||
def addEntry[T <: Entity: ClassTag](name: String, tier: Int, icon: IconDef, factory: () => T): Unit = {
|
||||
def addEntry[T <: Entity : ClassTag](name: String, tier: Int, icon: IconDef, factory: () => T): Unit = {
|
||||
val entry = Entry(name, tier, icon, factory)
|
||||
entries += entry
|
||||
val clazz = classTag[T].runtimeClass.getName
|
||||
@ -73,5 +74,8 @@ object CardRegistry {
|
||||
() => new DataCard.Tier2)
|
||||
addEntry("Data Card", 2, new IconDef("items/DataCard2", animation = DataCardAnimation),
|
||||
() => new DataCard.Tier3)
|
||||
|
||||
addEntry("Sound Card", 1, new IconDef("items/SoundCard", animation = DataCardAnimation),
|
||||
() => new SoundCard)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user