Skip to content

Commit 0ac5265

Browse files
committed
feat: add sound management to sprite, including parsing and serialization
1 parent ec86fcc commit 0ac5265

File tree

6 files changed

+104
-0
lines changed

6 files changed

+104
-0
lines changed

src/ast.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod proc;
1313
mod project;
1414
mod references;
1515
mod rotation_style;
16+
mod sound;
1617
mod sprite;
1718
mod stmt;
1819
mod struct_;
@@ -37,6 +38,7 @@ pub use proc::*;
3738
pub use project::*;
3839
pub use references::*;
3940
pub use rotation_style::*;
41+
pub use sound::*;
4042
pub use sprite::*;
4143
pub use stmt::*;
4244
pub use struct_::*;

src/ast/sound.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use std::path::Path;
2+
3+
use logos::Span;
4+
5+
use crate::misc::SmolStr;
6+
7+
#[derive(Debug)]
8+
pub struct Sound {
9+
pub name: SmolStr,
10+
pub path: SmolStr,
11+
pub span: Span,
12+
}
13+
14+
impl Sound {
15+
pub fn new(path: SmolStr, alias: Option<SmolStr>, span: Span) -> Self {
16+
let name = alias.unwrap_or_else(|| {
17+
Path::new(&*path)
18+
.file_stem()
19+
.unwrap()
20+
.to_str()
21+
.unwrap()
22+
.into()
23+
});
24+
Self { name, path, span }
25+
}
26+
}

src/ast/sprite.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::misc::SmolStr;
99
#[derive(Debug, Default)]
1010
pub struct Sprite {
1111
pub costumes: Vec<Costume>,
12+
pub sounds: Vec<Sound>,
1213
pub procs: FxHashMap<SmolStr, Proc>,
1314
pub proc_definitions: FxHashMap<SmolStr, Vec<Stmt>>,
1415
pub proc_references: FxHashMap<SmolStr, References>,

src/codegen/sb3.rs

+40
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99
path::Path,
1010
};
1111

12+
use base64::write;
1213
use fxhash::{
1314
FxHashMap,
1415
FxHashSet,
@@ -601,6 +602,11 @@ where T: Write + Seek
601602
}
602603
write!(self, "]")?; // costumes
603604
write!(self, r#","sounds":["#)?;
605+
let mut comma = false;
606+
for sound in &sprite.sounds {
607+
write_comma_io(&mut self.zip, &mut comma)?;
608+
self.sound(input, sound, d)?;
609+
}
604610
write!(self, "]")?; // sounds
605611
if let Some(x_position) = &sprite.x_position {
606612
let x_position = x_position.evaluate();
@@ -834,6 +840,40 @@ where T: Write + Seek
834840
write!(self, "}}") // costume
835841
}
836842

843+
pub fn sound(&mut self, input: &Path, sound: &Sound, d: D) -> io::Result<()> {
844+
let path = input.join(&*sound.path);
845+
let hash = self
846+
.costumes
847+
.get(&sound.path)
848+
.cloned()
849+
.map(Ok::<_, io::Error>)
850+
.unwrap_or_else(|| {
851+
let mut file = match File::open(&path) {
852+
Ok(file) => file,
853+
Err(error) => {
854+
d.report(DiagnosticKind::IOError(error), &sound.span);
855+
return Ok(Default::default());
856+
}
857+
};
858+
let mut hasher = Md5::new();
859+
io::copy(&mut file, &mut hasher)?;
860+
let hash: SmolStr = format!("{:x}", hasher.finalize()).into();
861+
self.costumes.insert(sound.path.clone(), hash.clone());
862+
Ok(hash)
863+
})?;
864+
let (_, extension) = sound.path.rsplit_once('.').unwrap_or_default();
865+
self.sound_entry(&sound.name, &hash, extension)
866+
}
867+
868+
pub fn sound_entry(&mut self, name: &str, hash: &str, extension: &str) -> io::Result<()> {
869+
write!(self, "{{")?;
870+
write!(self, r#""name":{}"#, json!(name))?;
871+
write!(self, r#","assetId":"{hash}""#)?;
872+
write!(self, r#","dataFormat":"{extension}""#)?;
873+
write!(self, r#","md5ext":"{hash}.{extension}""#)?;
874+
write!(self, "}}") // costume
875+
}
876+
837877
pub fn proc(&mut self, s: S, d: D, proc: &Proc, definition: &[Stmt]) -> io::Result<()> {
838878
let this_id = self.id.new_id();
839879
let prototype_id = self.id.new_id();

src/parser/grammar.lalrpop

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub Sprite = Declr*;
1515
Declr: () = {
1616
";",
1717
COSTUMES Comma<Costume> ";",
18+
SOUNDS Comma<Sound> ";",
1819
HIDE ";" => {
1920
sprite.hidden = true;
2021
},
@@ -121,6 +122,12 @@ Costume: () = {
121122
}
122123
}
123124

125+
Sound: () = {
126+
<l:@L> <path:STR> <r:@R> <alias:(AS <STR>)?> => {
127+
sprite.sounds.push(Sound::new(path, alias, l..r));
128+
}
129+
}
130+
124131
Stmts: Vec<Stmt> = "{" <(<Stmt> ";"*)*> "}";
125132

126133
Stmt: Stmt = {

src/visitor/pass0.rs

+28
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub fn visit_project(project: &mut Project) {
2121

2222
fn visit_sprite(sprite: &mut Sprite, mut stage: Option<&mut Sprite>) {
2323
visit_costumes(&mut sprite.costumes);
24+
visit_sounds(&mut sprite.sounds);
2425
for enum_ in sprite.enums.values_mut() {
2526
visit_enum(enum_);
2627
}
@@ -111,6 +112,33 @@ fn visit_costumes(new: &mut Vec<Costume>) {
111112
}
112113
}
113114

115+
fn visit_sounds(new: &mut Vec<Sound>) {
116+
let old: Vec<Sound> = std::mem::take(new);
117+
for sound in old {
118+
if let Some(suffix) = sound.name.strip_prefix("@ascii/") {
119+
new.extend((' '..='~').map(|ch| Sound {
120+
name: format!("{suffix}{ch}").into(),
121+
path: sound.path.clone(),
122+
span: sound.span.clone(),
123+
}));
124+
} else if sound.path.contains('*') {
125+
let mut sounds: Vec<Sound> = glob(&sound.path)
126+
.unwrap()
127+
.map(Result::unwrap)
128+
.map(|path| Sound {
129+
name: path.file_stem().unwrap().to_string_lossy().into(),
130+
path: path.to_string_lossy().into(),
131+
span: sound.span.clone(),
132+
})
133+
.collect();
134+
sounds.sort_by(|a, b| a.name.cmp(&b.name));
135+
new.extend(sounds);
136+
} else {
137+
new.push(sound);
138+
}
139+
}
140+
}
141+
114142
fn visit_stmts(stmts: &mut Vec<Stmt>, v: &mut V) {
115143
for stmt in stmts {
116144
visit_stmt(stmt, v);

0 commit comments

Comments
 (0)