1use crate::prelude::*;
5use serde::{Deserialize, Serialize};
6
7use alloc::borrow::Cow;
8
9use super::{
10 AudioManifest, Files, NO_LENGTH, from_source, ogg_length_ms, playable_problem, vorbis_format,
11 wav_format,
12};
13use crate::error::ContentError;
14use crate::format::{AssetKind, AssetPath, DecodeCx, Format, TEXT_MAX_BYTES, join, text};
15
16pub const MAX_SOUND_BYTES: usize = 64 << 20;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24pub struct Sound {
25 pub channels: u16,
27 pub sample_rate: u32,
29}
30
31impl AssetKind for Sound {
32 const NAME: &'static str = "sound";
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub struct MusicTrack {
38 pub channels: u16,
40 pub sample_rate: u32,
42 pub length_ms: u32,
44}
45
46impl AssetKind for MusicTrack {
47 const NAME: &'static str = "music track";
48}
49
50fn sound(
54 file: &AssetPath,
55 format: Option<(u16, u32)>,
56 not_one: &str,
57) -> Result<Sound, Vec<ContentError>> {
58 let error = |message: String| vec![ContentError::new(file.shown(), message)];
59 let (channels, sample_rate) = format.ok_or_else(|| error(not_one.to_owned()))?;
60 match playable_problem(channels, sample_rate) {
61 Some(problem) => Err(error(problem)),
62 None => Ok(Sound {
63 channels,
64 sample_rate,
65 }),
66 }
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
71pub struct Wav;
72
73impl Format for Wav {
74 type Asset = Sound;
75
76 fn id(&self) -> &'static str {
77 "wav"
78 }
79
80 fn extensions(&self) -> &'static [&'static str] {
81 &["wav"]
82 }
83
84 fn sniff(&self, bytes: &[u8]) -> bool {
85 bytes.get(..4) == Some(b"RIFF") && bytes.get(8..12) == Some(b"WAVE")
86 }
87
88 fn max_bytes(&self) -> usize {
89 MAX_SOUND_BYTES
90 }
91
92 fn decode(
93 &self,
94 file: &AssetPath,
95 bytes: &[u8],
96 _cx: &DecodeCx<'_>,
97 ) -> Result<Sound, Vec<ContentError>> {
98 sound(file, wav_format(bytes), "not a PCM WAV file")
99 }
100}
101
102const OGG_MAGIC: &[u8; 4] = b"OggS";
104
105#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
107pub struct OggVorbis;
108
109impl Format for OggVorbis {
110 type Asset = Sound;
111
112 fn id(&self) -> &'static str {
113 "ogg-vorbis"
114 }
115
116 fn extensions(&self) -> &'static [&'static str] {
117 &["ogg"]
118 }
119
120 fn sniff(&self, bytes: &[u8]) -> bool {
121 bytes.starts_with(OGG_MAGIC)
122 }
123
124 fn max_bytes(&self) -> usize {
125 MAX_SOUND_BYTES
126 }
127
128 fn decode(
129 &self,
130 file: &AssetPath,
131 bytes: &[u8],
132 _cx: &DecodeCx<'_>,
133 ) -> Result<Sound, Vec<ContentError>> {
134 sound(file, vorbis_format(bytes), "not an OGG Vorbis file")
135 }
136}
137
138#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
141pub struct OggVorbisTrack;
142
143impl Format for OggVorbisTrack {
144 type Asset = MusicTrack;
145
146 fn id(&self) -> &'static str {
147 "ogg-vorbis"
148 }
149
150 fn extensions(&self) -> &'static [&'static str] {
151 &["ogg"]
152 }
153
154 fn sniff(&self, bytes: &[u8]) -> bool {
155 bytes.starts_with(OGG_MAGIC)
156 }
157
158 fn max_bytes(&self) -> usize {
159 MAX_SOUND_BYTES
160 }
161
162 fn decode(
163 &self,
164 file: &AssetPath,
165 bytes: &[u8],
166 cx: &DecodeCx<'_>,
167 ) -> Result<MusicTrack, Vec<ContentError>> {
168 let Sound {
169 channels,
170 sample_rate,
171 } = OggVorbis.decode(file, bytes, cx)?;
172 let length_ms =
173 ogg_length_ms(bytes).ok_or_else(|| vec![ContentError::new(file.shown(), NO_LENGTH)])?;
174 Ok(MusicTrack {
175 channels,
176 sample_rate,
177 length_ms,
178 })
179 }
180}
181
182impl AssetKind for AudioManifest {
183 const NAME: &'static str = "audio manifest";
184}
185
186#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
193pub struct AudioManifestRon;
194
195impl Format for AudioManifestRon {
196 type Asset = AudioManifest;
197
198 fn id(&self) -> &'static str {
199 "audio-manifest"
200 }
201
202 fn extensions(&self) -> &'static [&'static str] {
203 &["ron"]
204 }
205
206 fn max_bytes(&self) -> usize {
207 TEXT_MAX_BYTES
208 }
209
210 fn decode(
211 &self,
212 file: &AssetPath,
213 bytes: &[u8],
214 cx: &DecodeCx<'_>,
215 ) -> Result<AudioManifest, Vec<ContentError>> {
216 let source = text(file, bytes)?;
217 let path = |name: &str| join(file.dir(), name);
218 let files = Files {
219 sound: &|name| cx.bytes(&path(name)).map(Cow::Borrowed),
220 sound_shown: &|name| cx.shown(&path(name)),
221 sound_left_out: &|name| cx.left_out(&path(name)),
222 music_dir: "",
223 music: None,
224 };
225 from_source(file.shown(), source, &files)
226 }
227}