Skip to main content

tokmd_fun/
lib.rs

1//! # tokmd-fun
2//!
3//! **Tier 3 (Novelty)**
4//!
5//! Fun renderers for tokmd analysis outputs. Provides creative visualizations
6//! like 3D code cities and audio representations.
7//!
8//! ## What belongs here
9//! * 3D code city visualization (OBJ format)
10//! * Audio representation (MIDI format)
11//! * Eco-label generation
12//! * Other novelty outputs
13//!
14//! ## What does NOT belong here
15//! * Serious analysis features
16//! * Analysis computation
17//! * Core receipt formatting
18
19use 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}