1225
|
1 type
|
|
2 Encoding {.size: sizeof(uint32).} = enum
|
|
3 # Unspecified = 0
|
|
4 # Uint8Ulaw = 1
|
|
5 # Int8 = 2
|
|
6 Int16 = 3
|
|
7 # Int24 = 4
|
|
8 # Int32 = 5
|
|
9 # Float32 = 6
|
|
10 # Float64 = 7
|
|
11
|
|
12 AuHeader = object
|
|
13 magicNumber: uint32
|
|
14 dataOffset: uint32
|
|
15 dataSize: uint32
|
|
16 encoding: Encoding
|
|
17 sampleRate: uint32
|
|
18 channels: uint32
|
|
19
|
|
20 proc readSample(stream: Stream, encoding: Encoding, channels: int): Sample =
|
|
21 result[0] = stream.readint16()
|
|
22 swapEndian16(addr result[0], addr result[0])
|
|
23
|
|
24 if channels == 2:
|
|
25 result[1] = stream.readint16()
|
|
26 swapEndian16(addr result[1], addr result[1])
|
|
27 else:
|
|
28 result[1] = result[0]
|
|
29
|
|
30 # https://en.wikipedia.org/wiki/Au_file_format
|
|
31 proc ReadAU*(stream: Stream): SoundData =
|
|
32 var header: AuHeader
|
|
33
|
|
34 for name, value in fieldPairs(header):
|
|
35 var bytes: array[4, uint8]
|
|
36 stream.read(bytes)
|
|
37 swap(bytes[0], bytes[3])
|
|
38 swap(bytes[1], bytes[2])
|
|
39 value = cast[typeof(value)](bytes)
|
|
40
|
|
41 assert header.magicNumber == 0x2e736e64
|
|
42 if header.sampleRate != AUDIO_SAMPLE_RATE:
|
|
43 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>)")
|
|
44 if not (header.channels in [1'u32, 2'u32]):
|
|
45 raise newException(Exception, "Only support mono and stereo audio at the moment (1 or 2 channels), but found " & $header.channels)
|
|
46
|
|
47 var annotation: string
|
|
48 stream.read(annotation)
|
|
49
|
|
50 stream.setPosition(int(header.dataOffset))
|
|
51 while not stream.atEnd():
|
|
52 result.add stream.readSample(header.encoding, int(header.channels))
|
|
53
|
|
54 {.compile: currentSourcePath.parentDir() & "/stb_vorbis.c".}
|
|
55
|
|
56 proc stb_vorbis_decode_memory(mem: pointer, len: cint, channels: ptr cint, sample_rate: ptr cint, output: ptr ptr cshort): cint {.importc.}
|
|
57 proc free(p: pointer) {.importc.}
|
|
58
|
|
59 proc ReadVorbis*(stream: Stream): SoundData =
|
|
60 var
|
|
61 data = stream.readAll()
|
|
62 channels: cint
|
|
63 sampleRate: cint
|
|
64 output: ptr cshort
|
|
65
|
|
66 var nSamples = stb_vorbis_decode_memory(addr data[0], cint(data.len), addr channels, addr sampleRate, addr output)
|
|
67
|
|
68 if nSamples < 0:
|
|
69 raise newException(Exception, &"Unable to read ogg/vorbis sound file, error code: {nSamples}")
|
|
70 if sampleRate != AUDIO_SAMPLE_RATE:
|
|
71 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>)")
|
|
72
|
|
73 if channels == 2:
|
|
74 result.setLen(int(nSamples))
|
|
75 copyMem(addr result[0], output, nSamples * sizeof(Sample))
|
|
76 free(output)
|
|
77 elif channels == 1:
|
|
78 for i in 0 ..< nSamples:
|
|
79 let value = cast[ptr UncheckedArray[int16]](output)[i]
|
|
80 result.add [value, value]
|
|
81 free(output)
|
|
82 else:
|
|
83 free(output)
|
|
84 raise newException(Exception, "Only support mono and stereo audio at the moment (1 or 2 channels), but found " & $channels)
|
|
85
|
|
86 proc LoadAudio*(path: string, package = DEFAULT_PACKAGE): SoundData =
|
|
87 if path.splitFile().ext.toLowerAscii == ".au":
|
|
88 loadResource_intern(path, package = package).ReadAU()
|
|
89 elif path.splitFile().ext.toLowerAscii == ".ogg":
|
|
90 loadResource_intern(path, package = package).ReadVorbis()
|
|
91 else:
|
|
92 raise newException(Exception, "Unsupported audio file type: " & path)
|
|
93
|