#![allow(unused_variables, unused_imports)]
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use fluffl::{
audio::{
mixer::{
protocol::{MixerEventKind, MixerRequest, MixerResponse, OffsetKind, TrackID},
standard::MixerAudioDeviceContext,
streams::{ExplicitWave, ImplicitWave, ScaleMode},
HasAudioStream, Mixer, MixerProtocol, MutatedResult, SampleTime,
},
Interval,
},
collections::fixed_stack::FixedStack,
console::*,
gui::*,
io::*,
math::{Vec2, Vec4, WaveKind, FP32, FP64},
prelude::*,
text_writer::{self, HasTextWriterBuilder, TextWriter, UROOB},
window::{event_util::*, *},
*,
};
struct PathSegment {
a: Vec2<f32>,
b: Vec2<f32>,
t_lo: f32,
t_hi: f32,
}
impl PathSegment {
pub fn new(a: Vec2<f32>, b: Vec2<f32>) -> Self {
Self {
a,
b,
t_lo: 0.0,
t_hi: 0.0,
}
}
pub fn with_time(mut self, lo: f32, hi: f32) -> Self {
self.t_hi = hi;
self.t_lo = lo;
self
}
pub fn eval(&self, global_time: f32) -> Vec2<f32> {
let local_time = global_time - self.t_lo;
let duration = self.t_hi - self.t_lo;
let when_gt_zero = (0.0 - local_time).to_bits() as i32 >> 31;
let when_lt_durtation = (local_time - duration) as i32 >> 31;
let mask =
f32::from_bits((1.0f32.to_bits() as i32 & (when_gt_zero & when_lt_durtation)) as u32);
let t = local_time / duration;
((self.a * (1.0 - t)) + (self.b * t)) * mask
}
}
pub struct MainState {
pub mixer_device: MixerAudioDeviceContext,
pub stream_queue: Vec<Option<Box<dyn HasAudioStream>>>,
pub is_key_down: HashSet<KeyCode>,
pub key_extend_list: Vec<(KeyCode, TrackID)>,
pub key_frequency_table: HashMap<KeyCode, f32>,
pub t: f32,
pub pos_x: f32,
pub pos_y: f32,
pub mixer_time: SampleTime,
pub writer: TextWriter,
pub wave_type: WaveKind,
pub gui_manager: GuiManager<FlufflState<MainState>>,
pub mutated_text: String,
pub tracks_to_delete_table: HashSet<TrackID>,
pub angle: FP32,
}
#[fluffl(Debug)]
pub async fn main() {
math::waves::noise::init();
let raw_bytes = load_file!("./wasm_bins/resources/config.xml").expect("config failed to load");
let config_text = String::from_utf8(raw_bytes).expect("config file currupted");
let window = FlufflWindow::init(config_text.as_str()).expect("failed to init window");
let gl = window.gl();
unsafe {
window.gl().clear_color(0.1, 0.1, 0.1, 1.);
window
.gl()
.viewport(0, 0, window.width() as i32, window.height() as i32);
}
let atlas_bytes = load_file!("./wasm_bins/resources/font.bcode").expect("file not found");
let atlas = HieroAtlas::deserialize(crate::decoders::base64::decode(UROOB).unwrap())
.ok()
.expect("font parse failed");
let ctx = window.audio_context();
let mixer_device = MixerAudioDeviceContext::new(ctx);
mixer_device.resume();
let app_state = MainState {
gui_manager: setup_test_gui(GuiManager::new(gl.clone())),
key_frequency_table: vec![
(KeyCode::KEY_A, 262.0),
(KeyCode::KEY_S, 294.0),
(KeyCode::KEY_D, 330.0),
(KeyCode::KEY_F, 349.0),
(KeyCode::KEY_H, 10_000.0),
(KeyCode::KEY_J, 100.0),
]
.into_iter()
.collect::<HashMap<_, _>>(),
mixer_device,
t: 0.,
pos_x: 0.,
pos_y: 0.,
stream_queue: vec![],
writer: TextWriter::new(&gl).with_atlas(atlas).build(),
is_key_down: HashSet::new(),
key_extend_list: Vec::new(),
mixer_time: SampleTime::new(),
wave_type: WaveKind::Square,
mutated_text: String::new(),
tracks_to_delete_table: HashSet::new(),
angle: FP32::from_bits(20000),
};
FlufflWindow::run(window, app_state, main_loop);
}
async fn main_loop(
win_ptr: FlufflWindowPtr,
running: FlufflRunning,
main_state: FlufflState<MainState>,
) {
process_events(&win_ptr, running, &main_state).await;
draw_scene(&win_ptr, running, &main_state).await;
handle_mixer_responses(&win_ptr, running, &main_state).await;
execute_gui_mutation_requests(&win_ptr, running, &main_state).await;
}
async fn process_events(
win_ptr: &FlufflWindowPtr,
mut running: FlufflRunning,
main_state: &FlufflState<MainState>,
) {
let main_state = &mut *main_state.borrow_mut();
let mixer_device = &mut main_state.mixer_device;
let writer = &mut main_state.writer;
let is_key_down = &mut main_state.is_key_down;
let key_extend_list = &mut main_state.key_extend_list;
let wave_type = &mut main_state.wave_type;
let key_frequency_table = &mut main_state.key_frequency_table;
let gui_manager = &mut main_state.gui_manager;
let gl = win_ptr.window().gl();
main_state.t += 0.01;
let t = main_state.t;
let x = main_state.pos_x;
let y = main_state.pos_y;
let max_seek_time_ms = 300_000.0;
let seek_time = (x / 500.0) * max_seek_time_ms;
for event in win_ptr.window_mut().get_events().flush_iter_mut() {
gui_manager.push_event(event);
match event {
EventKind::Quit => running.set(false),
EventKind::Resize { width, height } => {
console_log!("resized: [{}x{}]\n", width, height);
unsafe {
gl.viewport(0, 0, width, height);
}
}
EventKind::KeyDown { code } => {
let code_char = code.key_val().unwrap_or_default();
if let '0'..='9' = code_char {
let offset = code_char as u8 as usize - b'0' as usize;
let new_type = WaveKind::from(offset);
*wave_type = new_type;
}
if let KeyCode::KEY_T = code {
let file_pointer_to_music =
std::fs::File::open("./wasm_bins/resources/fuck_jannies.adhoc")
.expect("file galed to load");
let parsed_music_file = adhoc_audio::AdhocCodec::load(file_pointer_to_music)
.expect("failed to read music file");
let id = mixer_device.gen_id();
mixer_device.send_request(MixerRequest::AddTrack(
id,
OffsetKind::current(),
Box::new(ExplicitWave::new(parsed_music_file, ScaleMode::Repeat)),
));
}
if !is_key_down.contains(&code) {
if let Some(&wave_frequency) = key_frequency_table.get(&code) {
let wave_frequency = wave_frequency as f64;
let id = mixer_device.gen_id();
mixer_device.send_request(MixerRequest::AddTrack(
id,
OffsetKind::current(),
Box::new(ImplicitWave::new(
wave_type.as_fn(),
Interval::from_length(FP64::from(1000)),
wave_frequency,
)),
));
key_extend_list.push((code, id));
}
}
if let KeyCode::KEY_V = code {
mixer_device
.send_request(MixerRequest::MutateMixer(TrackID::null(), |_, mixer| {
mixer.print_tree()
}));
}
if let KeyCode::PAGE_U = code {}
if let KeyCode::PAGE_D = code {}
if let KeyCode::SPACE = code {
unsafe {
gl.clear(glow::COLOR_BUFFER_BIT);
}
}
if let KeyCode::KEY_Y = code {
mixer_device.dump_recording();
}
if !is_key_down.contains(&code) {
console_log!("char = {}\n", code.key_val().unwrap());
is_key_down.insert(code);
}
}
EventKind::KeyUp { code } => {
is_key_down.remove(&code);
let if_code_is_in_extend_list = key_extend_list
.iter()
.enumerate()
.find(|(k, &(c, _))| c == code);
if let Some((key_idx, _)) = if_code_is_in_extend_list {
key_extend_list.remove(key_idx);
}
}
EventKind::MouseMove { x, y, .. } => {
main_state.pos_x = x as f32;
main_state.pos_y = y as f32;
}
EventKind::MouseDown {
button_code: MouseCode::LEFT_BUTTON,
..
} => {
println!("seeked at:{}", time_to_string(seek_time as i64));
mixer_device.send_request(MixerRequest::Seek(OffsetKind::Start {
offset: seek_time as u64,
}));
}
EventKind::MouseUp { button_code, x, y } => {
console_log!("mouse down at: [x:{},y:{}]\n", x, y);
console_log!("{}\n", button_code);
}
EventKind::MouseWheel { button_code } => {
console_log!("{}\n", button_code);
}
_ => (),
}
}
}
async fn draw_scene(
win_ptr: &FlufflWindowPtr,
running: FlufflRunning,
main_state: &FlufflState<MainState>,
) {
let main_state = &mut *main_state.borrow_mut();
let window_bounds = win_ptr.window_mut().get_bounds();
let mixer_device = &mut main_state.mixer_device;
let writer = &mut main_state.writer;
let is_key_down = &mut main_state.is_key_down;
let key_extend_list = &mut main_state.key_extend_list;
let wave_type = &mut main_state.wave_type;
let key_frequency_table = &mut main_state.key_frequency_table;
let gui_manager = &mut main_state.gui_manager;
let temp_text = &mut main_state.mutated_text;
let gl = win_ptr.window().gl();
let t = main_state.t;
let x = main_state.pos_x;
let y = main_state.pos_y;
unsafe {
gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT | glow::STENCIL_BUFFER_BIT);
gl.enable(glow::DEPTH_TEST);
gl.depth_func(glow::LEQUAL);
}
let (win_width, win_height) = win_ptr.window().get_bounds_f32();
let speed_t = ((FP64::from(x) - 0) / 200).clamp(FP64::from(0), FP64::from(1));
let speed = speed_t * 3;
let caption_list = [format!(
"{time:}x[{speed:.2}]",
time = time_to_string(main_state.mixer_time.elapsed_in_ms_fp().as_i64()),
speed = speed.as_f64()
)];
let t = main_state.angle.as_f32().max(1.0).min(7980.9);
let segments = [
PathSegment::new(
Vec2::from_array([100., 400.]),
Vec2::from_array([600., 400.]),
)
.with_time(0., 2000.),
PathSegment::new(
Vec2::from_array([600., 400.]),
Vec2::from_array([100., 100.]),
)
.with_time(2000., 4000.),
PathSegment::new(
Vec2::from_array([100., 100.]),
Vec2::from_array([100., 400.]),
)
.with_time(4000., 8000.),
];
let final_point: Vec2<f32> = segments.iter().map(|seg| seg.eval(t)).sum();
gui_manager.render(writer, win_width, win_height);
writer.draw_text_line(
".",
final_point.x(),
final_point.y(),
32.0,
Some(win_ptr.window().get_bounds()),
);
writer.draw_text_line(
format!("angle = {} full ={}", t, main_state.angle.data).as_str(),
0.,
0.,
32.0,
Some(win_ptr.window().get_bounds()),
);
writer.draw_text_line(temp_text, 0.0, 200.0, 32.0, Some(window_bounds));
}
async fn handle_mixer_responses(
win_ptr: &FlufflWindowPtr,
running: FlufflRunning,
main_state: &FlufflState<MainState>,
) {
let ms_clone = main_state.clone();
let main_state = &mut *main_state.borrow_mut();
let window_bounds = win_ptr.window_mut().get_bounds();
let mixer_device = &mut main_state.mixer_device;
let writer = &mut main_state.writer;
let is_key_down = &mut main_state.is_key_down;
let key_extend_list = &mut main_state.key_extend_list;
let wave_type = &mut main_state.wave_type;
let key_frequency_table = &mut main_state.key_frequency_table;
let gui_manager = &mut main_state.gui_manager;
let tracks_to_delete_table = &mut main_state.tracks_to_delete_table;
mixer_device.send_request(MixerRequest::FetchMixerTime);
let responses_iter = mixer_device.recieve_responses();
let mut tracks_to_delete = FixedStack::<32, TrackID>::new();
for resp in responses_iter {
match resp {
MixerResponse::MixerTime(t) => {
main_state.mixer_time = t;
}
MixerResponse::MixerEvent(MixerEventKind::TrackStopped(tid)) => {
if tracks_to_delete_table.contains(&tid) {
tracks_to_delete.push(tid);
tracks_to_delete_table.remove(&tid);
}
}
_ => (),
}
}
while let Some(tid) = tracks_to_delete.pop() {
mixer_device.send_request(MixerRequest::RemoveTrack(tid));
}
}
async fn execute_gui_mutation_requests(
win_ptr: &FlufflWindowPtr,
running: FlufflRunning,
main_state: &FlufflState<MainState>,
) {
GuiManager::execute_mutation_requests(main_state, |state| {
state.borrow_mut().gui_manager.poll_mutation_requsts()
})
}
fn time_to_string(elapsed_time_ms: i64) -> String {
let total_seconds = elapsed_time_ms / 1000;
let total_minutes = total_seconds / 60;
let total_hours = total_minutes / 60;
if total_seconds < 60 {
format!("{}s", total_seconds % 60)
} else if total_seconds < 3600 {
format!("{}m:{}s", total_minutes % 60, total_seconds % 60)
} else {
format!(
"{}h:{}m:{}s",
total_hours,
total_minutes % 60,
total_seconds % 60
)
}
}
fn setup_test_gui(
mut manager: GuiManager<FlufflState<MainState>>,
) -> GuiManager<FlufflState<MainState>> {
let origin = manager.add_component(GuiComponentKey::default(), Box::new(OriginState::new()));
let pink_frame = manager
.builder_frame()
.with_parent(origin)
.with_bounds([400.0 + 0.0, 200.0 + 100.0])
.with_roundness([1., 1., 10.0, 10.0])
.with_position([64.0, 32.0])
.with_edge_thickness(0.05)
.with_scrollbars(true)
.build();
let alt_frame = manager
.builder_frame()
.with_parent(origin)
.with_bounds([200.0, 100.0])
.with_roundness([0.0, 0.0, 10.0, 10.])
.with_position([64.0, 400.0])
.with_drag(true)
.build();
let red_frame = manager
.builder_frame()
.with_parent(pink_frame)
.with_bounds([400., 45.])
.with_color([0.7, 0.2, 0., 1.0])
.with_position([0.0, -45.0])
.with_roundness([15.0, 15.0, 0.0, 0.0])
.with_flags(component_flags::TITLEBAR | component_flags::OVERFLOWABLE)
.with_drag_highest(true)
.with_edge_thickness(0.00)
.build();
let red_child = manager
.builder_frame()
.with_parent(red_frame)
.with_bounds([32., 32.])
.with_roundness([16.0, 16.0, 0.0, 0.0])
.with_color(Vec4::rgb_u32(0x277BC0))
.with_position([2.0, 6.0])
.with_drag(true)
.build();
let orange_frame = manager
.builder_frame()
.with_parent(pink_frame)
.with_bounds([256., 128.])
.with_color(Vec4::rgb_u32(0xFF7F3F))
.with_roundness(Vec4::from([1., 1., 30., 30.]))
.with_edge_color([0., 0., 0., 1.0])
.with_position([128.0, 64.0])
.with_drag(true)
.with_visibility(false)
.build();
let slider_frame = manager
.builder_slider()
.with_parent(pink_frame)
.with_position([4.0, 64.0])
.with_bounds([400.0, 32.0])
.with_color(Vec4::rgb_u32(0x554994))
.with_edge_color(Vec4::rgb_u32(0xFFCCB3))
.with_roundness([8.0; 4])
.with_drag(false)
.with_listener(GuiEventKind::OnFocusIn, |state, _, _| {
state.slider_frame.edge_color = Vec4::rgb_u32(0xff0000);
})
.with_listener(GuiEventKind::OnFocusOut, |state, _, _| {
state.slider_frame.edge_color = Vec4::rgb_u32(0xFFCCB3);
})
.with_button_bounds([32.0, 120.0])
.with_button_color(Vec4::rgb_u32(0x332255))
.with_button_edge_color(Vec4::rgb_u32(0xF29393))
.with_button_roundness([8.0; 4])
.with_button_listener(GuiEventKind::OnHoverIn, |f, _, _| {
f.color *= 9. / 10.;
})
.with_button_listener(GuiEventKind::OnHoverOut, |f, _, _| {
f.color *= 10. / 9.;
})
.with_button_listener(GuiEventKind::OnMouseDown, |f, _, _| {
f.color = Vec4::from([1.0; 4]) - f.color;
})
.with_button_listener(GuiEventKind::OnMouseRelease, |f, _, _| {
f.color = Vec4::from([1.0; 4]) - f.color;
})
.with_button_listener_advanced(GuiEventKind::OnDrag, |info| {
let slider_button_key = info.key;
let gui_comp_tree = info.gui_comp_tree;
let slider_frame_key = gui_comp_tree
.get_parent_id(slider_button_key)
.expect("slider button should have parent");
let percentage = gui_comp_tree
.get(slider_frame_key)
.and_then(|comp| comp.as_any().downcast_ref::<SliderState>())
.map(|slider_frame| slider_frame.percentage)
.unwrap_or_default();
let disp = info.event.disp();
let new_angle = FP32::from_bits(((i32::MAX) as f64 * percentage as f64) as i32);
let can_beep = (percentage * 10000.0).fract() < 0.001
&& percentage > 0.05
&& percentage < 0.95
&& disp.y().abs() < 0.001;
if can_beep {
info.mutation_queue.enqueue(Box::new(move |state| {
let state = &mut *state.borrow_mut();
let mixer_device = &mut state.mixer_device;
let removal_table = &mut state.tracks_to_delete_table;
let new_track_id = mixer_device.gen_id();
state.angle = new_angle;
removal_table.insert(new_track_id);
mixer_device.send_request(MixerRequest::AddTrack(
new_track_id,
OffsetKind::current(),
Box::new(ImplicitWave::new(
WaveKind::SawTooth.as_fn(),
Interval::from_length(FP64::from(64)),
1200.0,
)),
))
}));
} else {
info.mutation_queue.enqueue(Box::new(move |state| {
let state = &mut *state.borrow_mut();
state.angle = new_angle;
}));
}
})
.build();
for k in 0..20 {
let row = k / 7;
let col = k % 7;
let color = Vec4::<f32>::rgb_u32(0x277BC0);
let _blue_button = manager
.builder_frame()
.with_name(format!("{}", (k as u8 + b'a') as char))
.with_parent(orange_frame)
.with_bounds([32., 32.])
.with_color(color)
.with_roundness(Vec4::from([1., 1., 1., 1.]))
.with_edge_color([0., 0., 0., 1.0])
.with_position([7.0 + 35.0 * (col as f32), 5.0 + 33.0 * (row as f32)])
.with_listener(GuiEventKind::OnHoverIn, |frame, _state, _| {
frame.color *= 0.5;
frame.color[3] = 1.0;
})
.with_listener(GuiEventKind::OnHoverOut, |frame, _state, _| {
frame.color *= 2.0;
frame.color[3] = 1.0;
})
.with_listener(GuiEventKind::OnMouseDown, |frame, _, mrq| {
frame.color = Vec4::rgb_u32(!0);
let name = frame.name().to_string();
if name == "a" {
mrq.enqueue(Box::new(|state| {
state.borrow_mut().mutated_text.clear();
}));
} else {
mrq.enqueue(Box::new(move |state| {
state.borrow_mut().mutated_text.push_str(name.as_str());
}));
}
})
.with_listener(GuiEventKind::OnMouseRelease, move |frame, _, _| {
frame.color = color * 0.5;
frame.color[3] = 1.0;
})
.with_drag(false)
.build();
}
let textbox_key = manager
.builder_textbox()
.with_parent(pink_frame)
.with_bounds([1000.0, 64.0])
.with_position([4.0, 200.0 - 64.0])
.with_color(Vec4::rgb_u32(0))
.with_roundness([0.0, 0.0, 32.0, 32.0])
.with_font_size(32.0)
.with_alignment([TextAlignment::Left, TextAlignment::Center])
.with_listener(GuiEventKind::OnFocusIn, |comp, _, _| {
comp.frame.edge_color = Vec4::rgb_u32(0xff0000);
})
.with_listener(GuiEventKind::OnFocusOut, |comp, _, _| {
comp.frame.edge_color = Vec4::rgb_u32(0x89CFFD);
})
.build();
let _title_label = manager
.builder_label()
.with_parent(red_frame)
.with_bounds([400.0, 45.0])
.with_position([0.0, 0.0])
.with_caption("its fucking over :-(")
.build();
manager
}