Mercurial > games > semicongine
diff semiconginev2/audio/resources.nim @ 1225:27cd1c21290e compiletime-tests
did: refactor resources
author | sam <sam@basx.dev> |
---|---|
date | Wed, 17 Jul 2024 22:20:59 +0700 |
parents | |
children | 841e12f33c47 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/semiconginev2/audio/resources.nim Wed Jul 17 22:20:59 2024 +0700 @@ -0,0 +1,93 @@ +type + Encoding {.size: sizeof(uint32).} = enum + # Unspecified = 0 + # Uint8Ulaw = 1 + # Int8 = 2 + Int16 = 3 + # Int24 = 4 + # Int32 = 5 + # Float32 = 6 + # Float64 = 7 + + AuHeader = object + magicNumber: uint32 + dataOffset: uint32 + dataSize: uint32 + encoding: Encoding + sampleRate: uint32 + channels: uint32 + +proc readSample(stream: Stream, encoding: Encoding, channels: int): Sample = + result[0] = stream.readint16() + swapEndian16(addr result[0], addr result[0]) + + if channels == 2: + result[1] = stream.readint16() + swapEndian16(addr result[1], addr result[1]) + else: + result[1] = result[0] + +# https://en.wikipedia.org/wiki/Au_file_format +proc ReadAU*(stream: Stream): SoundData = + var header: AuHeader + + for name, value in fieldPairs(header): + var bytes: array[4, uint8] + stream.read(bytes) + swap(bytes[0], bytes[3]) + swap(bytes[1], bytes[2]) + value = cast[typeof(value)](bytes) + + assert header.magicNumber == 0x2e736e64 + if header.sampleRate != AUDIO_SAMPLE_RATE: + raise newException(Exception, &"Only support sample rate of {AUDIO_SAMPLE_RATE} Hz but got {header.sampleRate} Hz, please resample (e.g. ffmpeg -i <infile> -ar {AUDIO_SAMPLE_RATE} <outfile>)") + if not (header.channels in [1'u32, 2'u32]): + raise newException(Exception, "Only support mono and stereo audio at the moment (1 or 2 channels), but found " & $header.channels) + + var annotation: string + stream.read(annotation) + + stream.setPosition(int(header.dataOffset)) + while not stream.atEnd(): + result.add stream.readSample(header.encoding, int(header.channels)) + +{.compile: currentSourcePath.parentDir() & "/stb_vorbis.c".} + +proc stb_vorbis_decode_memory(mem: pointer, len: cint, channels: ptr cint, sample_rate: ptr cint, output: ptr ptr cshort): cint {.importc.} +proc free(p: pointer) {.importc.} + +proc ReadVorbis*(stream: Stream): SoundData = + var + data = stream.readAll() + channels: cint + sampleRate: cint + output: ptr cshort + + var nSamples = stb_vorbis_decode_memory(addr data[0], cint(data.len), addr channels, addr sampleRate, addr output) + + if nSamples < 0: + raise newException(Exception, &"Unable to read ogg/vorbis sound file, error code: {nSamples}") + if sampleRate != AUDIO_SAMPLE_RATE: + raise newException(Exception, &"Only support sample rate of {AUDIO_SAMPLE_RATE} Hz but got {sampleRate} Hz, please resample (e.g. ffmpeg -i <infile> -acodec libvorbis -ar {AUDIO_SAMPLE_RATE} <outfile>)") + + if channels == 2: + result.setLen(int(nSamples)) + copyMem(addr result[0], output, nSamples * sizeof(Sample)) + free(output) + elif channels == 1: + for i in 0 ..< nSamples: + let value = cast[ptr UncheckedArray[int16]](output)[i] + result.add [value, value] + free(output) + else: + free(output) + raise newException(Exception, "Only support mono and stereo audio at the moment (1 or 2 channels), but found " & $channels) + +proc LoadAudio*(path: string, package = DEFAULT_PACKAGE): SoundData = + if path.splitFile().ext.toLowerAscii == ".au": + loadResource_intern(path, package = package).ReadAU() + elif path.splitFile().ext.toLowerAscii == ".ogg": + loadResource_intern(path, package = package).ReadVorbis() + else: + raise newException(Exception, "Unsupported audio file type: " & path) +