changeset 220:fab8980e4c29

add: loading *.au audio files
author Sam <sam@basx.dev>
date Sat, 13 May 2023 19:31:48 +0700
parents 972caf83930d
children 2a2367d289dd
files src/semicongine/audio.nim src/semicongine/core/audiotypes.nim src/semicongine/resources/audio.nim
diffstat 3 files changed, 74 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- 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]
--- 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
--- 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))