1use anyhow::Result;
20use midly::{Format, Header, MetaMessage, MidiMessage, Smf, Timing, TrackEvent, TrackEventKind};
21
22#[derive(Debug, Clone)]
23pub struct ObjBuilding {
24 pub name: String,
25 pub x: f32,
26 pub y: f32,
27 pub w: f32,
28 pub d: f32,
29 pub h: f32,
30}
31
32pub fn render_obj(buildings: &[ObjBuilding]) -> String {
33 let mut out = String::new();
34 out.push_str("# tokmd code city\n");
35 let mut vertex_index = 1usize;
36
37 for b in buildings {
38 out.push_str(&format!("o {}\n", sanitize_name(&b.name)));
39 let (x, y, z) = (b.x, b.y, 0.0f32);
40 let (w, d, h) = (b.w, b.d, b.h);
41
42 let v = [
43 (x, y, z),
44 (x + w, y, z),
45 (x + w, y + d, z),
46 (x, y + d, z),
47 (x, y, z + h),
48 (x + w, y, z + h),
49 (x + w, y + d, z + h),
50 (x, y + d, z + h),
51 ];
52 for (vx, vy, vz) in v {
53 out.push_str(&format!("v {} {} {}\n", vx, vy, vz));
54 }
55
56 let faces = [
57 [1, 2, 3, 4],
58 [5, 6, 7, 8],
59 [1, 2, 6, 5],
60 [2, 3, 7, 6],
61 [3, 4, 8, 7],
62 [4, 1, 5, 8],
63 ];
64 for face in faces {
65 out.push_str(&format!(
66 "f {} {} {} {}\n",
67 vertex_index + face[0] - 1,
68 vertex_index + face[1] - 1,
69 vertex_index + face[2] - 1,
70 vertex_index + face[3] - 1,
71 ));
72 }
73
74 vertex_index += 8;
75 }
76
77 out
78}
79
80fn sanitize_name(name: &str) -> String {
81 name.chars()
82 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
83 .collect()
84}
85
86#[derive(Debug, Clone)]
87pub struct MidiNote {
88 pub key: u8,
89 pub velocity: u8,
90 pub start: u32,
91 pub duration: u32,
92 pub channel: u8,
93}
94
95pub fn render_midi(notes: &[MidiNote], tempo_bpm: u16) -> Result<Vec<u8>> {
96 let ticks_per_quarter = 480u16;
97 let mut events: Vec<(u32, TrackEventKind<'static>)> = Vec::new();
98
99 let tempo = 60_000_000u32 / tempo_bpm.max(1) as u32;
100 events.push((0, TrackEventKind::Meta(MetaMessage::Tempo(tempo.into()))));
101
102 for note in notes {
103 let ch = note.channel.min(15).into();
104 events.push((
105 note.start,
106 TrackEventKind::Midi {
107 channel: ch,
108 message: MidiMessage::NoteOn {
109 key: note.key.into(),
110 vel: note.velocity.into(),
111 },
112 },
113 ));
114 events.push((
115 note.start + note.duration,
116 TrackEventKind::Midi {
117 channel: ch,
118 message: MidiMessage::NoteOff {
119 key: note.key.into(),
120 vel: 0.into(),
121 },
122 },
123 ));
124 }
125
126 events.sort_by_key(|e| e.0);
127
128 let mut track: Vec<TrackEvent> = Vec::new();
129 let mut last_time = 0u32;
130 for (time, kind) in events {
131 let delta = time.saturating_sub(last_time);
132 last_time = time;
133 track.push(TrackEvent {
134 delta: delta.into(),
135 kind,
136 });
137 }
138
139 track.push(TrackEvent {
140 delta: 0.into(),
141 kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
142 });
143
144 let smf = Smf {
145 header: Header::new(
146 Format::SingleTrack,
147 Timing::Metrical(ticks_per_quarter.into()),
148 ),
149 tracks: vec![track],
150 };
151
152 let mut out = Vec::new();
153 smf.write_std(&mut out)?;
154 Ok(out)
155}