Add sound card

This commit is contained in:
LeshaInc 2023-03-28 01:07:36 +03:00
parent e5b2e5c37c
commit 9643dc05c1
No known key found for this signature in database
GPG Key ID: 7F51850974C1C795
15 changed files with 376 additions and 195 deletions

@ -1 +1 @@
Subproject commit e7b477cf4d369d91132275237e72c80ad5440e40
Subproject commit 7b2f4ce827d79a7c231bf70c76c28469b705bb9e

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

View File

@ -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

View 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)
}
}

View File

@ -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))
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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")
}
}

View 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
}
}

View File

@ -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
}

View File

@ -0,0 +1,5 @@
package ocelot.desktop.audio
trait SoundStream {
def enqueue(samples: SoundSamples): Unit
}

View File

@ -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) ||

View File

@ -249,11 +249,10 @@ object UiHandler extends Logging {
if (Display.isCloseRequested)
OcelotDesktop.exit()
Audio.update()
OcelotDesktop.withTickLockAcquired {
updateWindowSizeAndPosition()
Audio.update()
KeyEvents.update()
MouseEvents.update()

View File

@ -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)
}