# HG changeset patch # User Sam # Date 1683981108 -25200 # Node ID f20965f1d7fab533232344d549f749c5d6ecb54e # Parent d3e62cd055d145bd339b17c90a1d131c39e5ed4c add: loading *.au audio files diff -r d3e62cd055d1 -r f20965f1d7fa src/semicongine/audio.nim --- a/src/semicongine/audio.nim Fri May 12 00:34:32 2023 +0700 +++ b/src/semicongine/audio.nim Sat May 13 19:31:48 2023 +0700 @@ -135,20 +135,22 @@ return false func applyLevel(sample: Sample, levelLeft, levelRight: Level): Sample = - (int16(float(sample[0]) * levelLeft), int16(float(sample[1]) * levelRight)) + [int16(float(sample[0]) * levelLeft), int16(float(sample[1]) * levelRight)] +func clip(value: int32): int16 = + int16(max(min(int32(high(int16)), value), int32(low(int16)))) + +# used for combining sounds func mix(a, b: Sample): Sample = - var - left = int32(a[0]) + int32(b[0]) - right = int32(a[1]) + int32(b[1]) - left = max(min(int32(high(int16)), left), int32(low(int16))) - right = max(min(int32(high(int16)), right), int32(low(int16))) - (int16(left), int16(right)) + [ + clip(int32(a[0]) + int32(b[0])), + clip(int32(a[1]) + int32(b[1])), + ] proc updateSoundBuffer(mixer: var Mixer) = # mix for i in 0 ..< mixer.buffers[mixer.currentBuffer].len: - var currentSample = (0'i16, 0'i16) + var currentSample = [0'i16, 0'i16] mixer.lock.withLock(): for track in mixer.tracks.mvalues: var stoppedSounds: seq[uint64] diff -r d3e62cd055d1 -r f20965f1d7fa src/semicongine/core/audiotypes.nim --- a/src/semicongine/core/audiotypes.nim Fri May 12 00:34:32 2023 +0700 +++ b/src/semicongine/core/audiotypes.nim Sat May 13 19:31:48 2023 +0700 @@ -9,7 +9,7 @@ type Level* = 0'f .. 1'f - Sample* = (int16, int16) + Sample* = array[2, int16] SoundData* = seq[Sample] Sound* = ref SoundData @@ -24,7 +24,7 @@ for i in 0 ..< int(float(rate) * len): let t = dt * float(i) let value = int16(sine(t) * float(high(int16))) - result.add (value, value) + result.add [value, value] proc newSound*(data: SoundData): Sound = result = new Sound diff -r d3e62cd055d1 -r f20965f1d7fa src/semicongine/resources/audio.nim --- a/src/semicongine/resources/audio.nim Fri May 12 00:34:32 2023 +0700 +++ b/src/semicongine/resources/audio.nim Sat May 13 19:31:48 2023 +0700 @@ -1,7 +1,68 @@ import std/streams +import std/endians +import std/math import ../core/audiotypes +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 + +func changeEndian(value: int16): int16 = + + var bytes: array[2, uint8] = cast[array[2, uint8]](value) + swap(bytes[0], bytes[1]) + result = cast[int16](bytes) + +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 +# Returns sound data and samplerate proc readAU*(stream: Stream): Sound = - result + 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 48000 Hz but got " & $header.sampleRate & " Hz, please resample (e.g. ffmpeg -i {infile} -ar 48000 {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) + + result = new Sound + stream.setPosition(int(header.dataOffset)) + while not stream.atEnd(): + result[].add stream.readSample(header.encoding, int(header.channels))