makepad_example_ironfish/app.rs
1use crate::{
2 makepad_widgets::*,
3 makepad_audio_graph::*,
4
5 makepad_synth_ironfish::ironfish::*,
6 makepad_audio_widgets::piano::*,
7 sequencer::*,
8 makepad_audio_widgets::display_audio::*
9};
10
11//use std::fs::File;
12//use std::io::prelude::*;
13live_design!{
14 import makepad_widgets::base::*
15 import makepad_widgets::theme_desktop_dark::*
16 import makepad_example_ironfish::app_desktop::AppDesktop
17 import makepad_example_ironfish::app_mobile::AppMobile
18
19 import makepad_audio_graph::mixer::Mixer;
20 import makepad_audio_graph::instrument::Instrument;
21 import makepad_synth_ironfish::ironfish::IronFish;
22 import makepad_widgets::designer::Designer;
23
24 //import makepad_example_fractal_zoom::mandelbrot::Mandelbrot;
25 //import makepad_example_numbers::number_grid::NumberGrid;
26 // APP
27 //ui: <AppMobile> {}
28 App = {{App}} {
29
30 audio_graph: {
31 root: <Mixer> {
32 c1 = <Instrument> {
33 <IronFish> {}
34 }
35 }
36 }
37 ui: <Window> {
38 window: {inner_size: vec2(1280, 1000)},
39 pass: {clear_color: #2A}
40 block_signal_event: true;
41 body = <AppDesktop> {}
42 }
43
44 /*
45 ui= <MultiWindow> {
46 mobile =<DesktopWindow> {
47 window: {inner_size: vec2(1280, 1000), dpi_override:2},
48 pass: {clear_color: #2A}
49 block_signal_event: true;
50 <AppDesktop> {}
51 }
52 <DesktopWindow> {
53 window: {position: vec2(0, 400), inner_size: vec2(800, 800)},
54 pass: {clear_color: #2A}
55 block_signal_event: true;
56 padding: {top: 30},
57 <Designer> {}
58 }
59 <DesktopWindow> {
60 window: {position: vec2(0, 0), inner_size: vec2(400, 800)},
61 pass: {clear_color: #2A}
62 block_signal_event: true;
63 <AppMobile> {}
64 }
65 }*/
66/*
67 ui=<DesktopWindow> {
68 window: {inner_size: vec2(1920, 1080)},
69
70 pass: {clear_color: #2A}
71 block_signal_event: true;
72 <SlidesView> {
73 goal_pos: 0.0
74
75 <SlideChapter> {
76 title = {text: "MAKEPAD.\nDESIGNING MODERN\nUIs FOR RUST."},
77 <SlideBody> {text: "Rik Arends\n"}
78 }
79 <Slide> {
80 title = {text: "A long long time ago …"},
81 <SlideBody> {text: "… in a galaxy nearby\n Cloud9 IDE & ACE"}
82 }
83 <Slide> {
84 title = {text: "HTML as an IDE UI?\nMadness!"},
85 <SlideBody> {text: "- Integrating design and code was hard\n- Could not innovate editing\n- Too slow, too hard to control"}
86 }
87 <Slide> {
88 title = {text: "Let's start over!"},
89 <SlideBody> {text: "- JavaScript and WebGL for UI\n- Write shaders to style UI\n- A quick demo"}
90 }
91 <Slide> {
92 title = {text: "Maybe JavaScript\nwas the problem?"},
93 <SlideBody> {text: "- Great livecoding, but …\n- Chrome crashing tabs after 30 minutes\n- Too slow"}
94 }
95 <Slide> {
96 title = {text: "Rust appears"},
97 <SlideBody> {text: "- Let's try again: Native + Wasm\n- Makepad in Rust\n- Startup with Eddy and Sebastian"}
98 }
99 <Slide> {title = {text: "Rust is fast: SIMD Mandelbrot"},
100 align: {x: 0.0, y: 0.5 flow: Down, spacing: 10, padding: 50}
101 draw_bg: { color: #x1A, radius: 5.0 }
102 <View>{
103 padding: 0, align:{x:0.5 spacing: 20}
104 <RoundedView>{
105 draw_bg: { color: #x2A }
106 margin: 0.0
107 padding: 0.0
108 <Mandelbrot> {width:Fill, height:Fill}
109 }
110 }
111 }
112
113 <Slide> {title = {text: "Instanced rendering"},
114 align: {x: 0.0, y: 0.5 flow: Down, spacing: 10, padding: 50}
115 draw_bg: { color: #x1A, radius: 5.0 }
116 <View>{
117 padding: 0, align:{x:0.5 spacing: 20}
118 <RoundedView>{
119 draw_bg: { color: #x2A }
120 margin: 0.0
121 padding: 0.0
122 <NumberGrid> {width:Fill, height:Fill}
123 }
124 }
125 }
126
127 <Slide> {
128 title = {text: "Our goal:\nUnify coding and UI design again."},
129 <SlideBody> {text: "As it was in Visual Basic.\nNow with modern design."}
130 }
131
132 <Slide> {title = {text: "Ironfish Desktop"},
133 <RoundedView>{
134 draw_bg: { color: #x2A }
135 margin: 10.0, width: 1600
136 padding: 0.0
137 <AppDesktop> {}
138 }
139 }
140
141 <Slide> {title = {text: "Ironfish Mobile"},
142 <View>{
143 padding: 0, align:{x:0.5}
144 margin: { top: 0 }
145 <AppMobile> {width:400, height: Fill}
146 }
147 }
148
149 <Slide> {title = {text: "Multi modal"},
150 <View>{
151 padding: 0, align:{x:0.5 spacing: 20}
152
153 <AppMobile> {width:400, height: Fill}
154
155 <RoundedView>{
156 draw_bg: { color: #x2A }
157 margin: 0.0
158 padding: 0.0
159 <AppDesktop> {
160 width: Fill, height: Fill
161 }
162 }
163 }
164 }
165
166 <Slide> {title = {text: "Visual design"},
167 align: {x: 0.0, y: 0.5 flow: Down, spacing: 10, padding: 50}
168 <View>{
169 padding: 0, align:{x:0.5 spacing: 20}
170 <RoundedView>{
171 draw_bg: { color: #x2A }
172 margin: 0.0
173 padding: 0.0
174 <AppDesktop> {width:900}
175 }
176
177 <RoundedView>{
178 draw_bg: { color: #x2A }
179 margin: 0.0
180 padding: 0.0
181 <Designer> {width:900}
182 }
183 }
184 }
185
186 <Slide> {
187 title = {text: "Our UI language: Live."},
188 <SlideBody> {text: "- Live editable\n- Design tool manipulates text\n- Inheritance structure\n- Rust-like module system"}
189 }
190
191 <Slide> {
192 title = {text: "These slides are a Makepad app"},
193 <SlideBody> {text: "- Show source\n"}
194 <SlideBody> {text: "- Show Rust API\n"}
195 }
196
197 <Slide> {
198 title = {text: "Future"},
199 <SlideBody> {text: "- Release of 0.4.0 soon\n- Windows, Linux, Mac, Web and Android\n- github.com/makepad/makepad\n- twitter: @rikarends @makepad"}
200 }
201
202 <Slide> {
203 title = {text: "Build for Android"},
204 <SlideBody> {text: "- SDK installer\n- Cargo makepad android\n"}
205 }
206 }
207 }*/
208 }
209}
210app_main!(App);
211
212pub struct SynthPreset {
213 pub id: LiveId,
214 pub name: String,
215 pub fav: bool,
216}
217
218#[derive(Live)]
219pub struct App {
220 #[live] ui: WidgetRef,
221 #[rust] _presets: Vec<SynthPreset>,
222 #[live] audio_graph: AudioGraph,
223 #[rust] midi_input: MidiInput,
224}
225
226impl LiveHook for App {
227 fn before_live_design(cx: &mut Cx) {
228 crate::makepad_audio_widgets::live_design(cx);
229 crate::makepad_audio_graph::live_design(cx);
230 crate::makepad_synth_ironfish::live_design(cx);
231 crate::sequencer::live_design(cx);
232 crate::app_desktop::live_design(cx);
233 crate::app_mobile::live_design(cx);
234 //makepad_example_fractal_zoom::mandelbrot::live_design(cx);
235 //makepad_example_numbers::number_grid::live_design(cx);
236 }
237}
238
239impl App {
240
241 pub fn data_bind(&mut self, mut db: DataBindingMap) {
242 // sequencer
243 db.bind(id!(sequencer.playing), ids!(playpause));
244 db.bind(id!(sequencer.bpm), ids!(speed.slider));
245 db.bind(id!(sequencer.rootnote), ids!(rootnote.dropdown));
246 db.bind(id!(sequencer.scale), ids!(scaletype.dropdown));
247 db.bind(id!(arp.enabled), ids!(arp.checkbox));
248 db.bind(id!(arp.octaves), ids!(arpoctaves.slider));
249
250 // Mixer panel
251 db.bind(id!(osc_balance), ids!(balance.slider));
252 db.bind(id!(noise), ids!(noise.slider));
253 db.bind(id!(sub_osc), ids!(sub.slider));
254 db.bind(id!(portamento), ids!(porta.slider));
255
256 // DelayFX Panel
257 db.bind(id!(delay.delaysend), ids!(delaysend.slider));
258 db.bind(id!(delay.delayfeedback), ids!(delayfeedback.slider));
259
260 db.bind(id!(bitcrush.enable), ids!(crushenable.checkbox));
261 db.bind(id!(bitcrush.amount), ids!(crushamount.slider));
262
263 db.bind(id!(delay.difference), ids!(delaydifference.slider));
264 db.bind(id!(delay.cross), ids!(delaycross.slider));
265
266 // Chorus panel
267 db.bind(id!(chorus.mix), ids!(chorusmix.slider));
268 db.bind(id!(chorus.mindelay), ids!(chorusdelay.slider));
269 db.bind(id!(chorus.moddepth), ids!(chorusmod.slider));
270 db.bind(id!(chorus.rate), ids!(chorusrate.slider));
271 db.bind(id!(chorus.phasediff), ids!(chorusphase.slider));
272 db.bind(id!(chorus.feedback), ids!(chorusfeedback.slider));
273
274 // Reverb panel
275 db.bind(id!(reverb.mix), ids!(reverbmix.slider));
276 db.bind(id!(reverb.feedback), ids!(reverbfeedback.slider));
277
278 //LFO Panel
279 db.bind(id!(lfo.rate), ids!(rate.slider));
280 db.bind(id!(filter1.lfo_amount), ids!(lfoamount.slider));
281 db.bind(id!(lfo.synconkey), ids!(sync.checkbox));
282
283 //Volume Envelope
284 db.bind(id!(volume_envelope.a), ids!(vol_env.attack.slider));
285 db.bind(id!(volume_envelope.h), ids!(vol_env.hold.slider));
286 db.bind(id!(volume_envelope.d), ids!(vol_env.decay.slider));
287 db.bind(id!(volume_envelope.s), ids!(vol_env.sustain.slider));
288 db.bind(id!(volume_envelope.r), ids!(vol_env.release.slider));
289
290 //Mod Envelope
291 db.bind(id!(mod_envelope.a), ids!(mod_env.attack.slider));
292 db.bind(id!(mod_envelope.h), ids!(mod_env.hold.slider));
293 db.bind(id!(mod_envelope.d), ids!(mod_env.decay.slider));
294 db.bind(id!(mod_envelope.s), ids!(mod_env.sustain.slider));
295 db.bind(id!(mod_envelope.r), ids!(mod_env.release.slider));
296 db.bind(id!(filter1.envelope_amount), ids!(modamount.slider));
297
298 // Filter panel
299 //db.bind(id!(filter1.filter_type), ids!(filter_type.dropdown));
300 db.bind(id!(filter1.cutoff), ids!(cutoff.slider));
301 db.bind(id!(filter1.resonance), ids!(resonance.slider));
302
303 // Osc1 panel
304 db.bind(id!(supersaw1.spread), ids!(osc1.supersaw.spread.slider));
305 db.bind(id!(supersaw1.diffuse), ids!(osc1.supersaw.diffuse.slider));
306 db.bind(id!(supersaw1.spread), ids!(osc1.supersaw.spread.slider));
307 db.bind(id!(supersaw1.diffuse), ids!(osc1.supersaw.diffuse.slider));
308 db.bind(id!(supersaw1.spread), ids!(osc1.hypersaw.spread.slider));
309 db.bind(id!(supersaw1.diffuse), ids!(osc1.hypersaw.diffuse.slider));
310
311 db.bind(id!(osc1.osc_type), ids!(osc1.type.dropdown));
312 db.bind(id!(osc1.transpose), ids!(osc1.transpose.slider));
313 db.bind(id!(osc1.detune), ids!(osc1.detune.slider));
314 db.bind(id!(osc1.harmonic), ids!(osc1.harmonicshift.slider));
315 db.bind(id!(osc1.harmonicenv), ids!(osc1.harmonicenv.slider));
316 db.bind(id!(osc1.harmoniclfo), ids!(osc1.harmoniclfo.slider));
317
318 // Osc2 panel
319 db.bind(id!(supersaw2.spread), ids!(osc2.supersaw.spread.slider));
320 db.bind(id!(supersaw2.diffuse), ids!(osc2.supersaw.diffuse.slider));
321 db.bind(id!(supersaw2.spread), ids!(osc2.supersaw.spread.slider));
322 db.bind(id!(supersaw2.diffuse), ids!(osc2.supersaw.diffuse.slider));
323 db.bind(id!(supersaw2.spread), ids!(osc2.hypersaw.spread.slider));
324 db.bind(id!(supersaw2.diffuse), ids!(osc2.hypersaw.diffuse.slider));
325
326 db.bind(id!(osc2.osc_type), ids!(osc2.type.dropdown));
327 db.bind(id!(osc2.transpose), ids!(osc2.transpose.slider));
328 db.bind(id!(osc2.detune), ids!(osc2.detune.slider));
329 db.bind(id!(osc2.harmonic), ids!(osc2.harmonicshift.slider));
330 db.bind(id!(osc2.harmonicenv), ids!(osc2.harmonicenv.slider));
331 db.bind(id!(osc2.harmoniclfo), ids!(osc2.harmoniclfo.slider));
332
333 // sequencer
334 db.bind(id!(sequencer.steps), ids!(sequencer));
335
336 db.apply(id!(osc1.osc_type), ids!(osc1.supersaw, visible), | v | v.enum_eq(id!(SuperSaw)));
337 db.apply(id!(osc2.osc_type), ids!(osc2.supersaw, visible), | v | v.enum_eq(id!(SuperSaw)));
338 db.apply(id!(osc1.osc_type), ids!(osc1.hypersaw, visible), | v | v.enum_eq(id!(HyperSaw)));
339 db.apply(id!(osc2.osc_type), ids!(osc2.hypersaw, visible), | v | v.enum_eq(id!(HyperSaw)));
340 db.apply(id!(osc1.osc_type), ids!(osc1.harmonic, visible), | v | v.enum_eq(id!(HarmonicSeries)));
341 db.apply(id!(osc2.osc_type), ids!(osc2.harmonic, visible), | v | v.enum_eq(id!(HarmonicSeries)));
342
343 db.apply(id!(mod_envelope.a), ids!(mod_env.display, draw_bg.attack), | v | v);
344 db.apply(id!(mod_envelope.h), ids!(mod_env.display, draw_bg.hold), | v | v);
345 db.apply(id!(mod_envelope.d), ids!(mod_env.display, draw_bg.decay), | v | v);
346 db.apply(id!(mod_envelope.s), ids!(mod_env.display, draw_bg.sustain), | v | v);
347 db.apply(id!(mod_envelope.r), ids!(mod_env.display, draw_bg.release), | v | v);
348 db.apply(id!(volume_envelope.a), ids!(vol_env.display, draw_bg.attack), | v | v);
349 db.apply(id!(volume_envelope.h), ids!(vol_env.display, draw_bg.hold), | v | v);
350 db.apply(id!(volume_envelope.d), ids!(vol_env.display, draw_bg.decay), | v | v);
351 db.apply(id!(volume_envelope.s), ids!(vol_env.display, draw_bg.sustain), | v | v);
352 db.apply(id!(volume_envelope.r), ids!(vol_env.display, draw_bg.release), | v | v);
353 }
354}
355
356impl AppMain for App {
357 fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
358
359 //let preset_lists = self.ui.swipe_list_set(ids!(preset_list));
360
361 if let Event::Draw(event) = event {
362 let cx = &mut Cx2d::new(cx, event);
363 while let Some(_next) = self.ui.draw_widget(cx).hook_widget() {
364 /*if let Some(mut list) = preset_lists.has_widget(&next).borrow_mut() {
365 for i in 0..10 {
366 if let Some(item) = list.get_entry(cx, LiveId(i as u64).into(), live_id!(Entry)) {
367 item.button(id!(label)).set_text(&format!("Button id {i}"));
368 item.draw_widget_all(cx);
369 }
370 }
371 }*/
372 }
373 return
374 }
375 let ui = self.ui.clone();
376 let mut synth_db = DataBindingStore::new();
377 let mut actions = ui.handle_widget_event(cx, event);
378
379 // handle preset lists events
380 /*for list in preset_lists.iter() {
381 for item in list.items_with_actions(&actions).iter() {
382 // check for actions inside the list item
383 if item.button(id!(delete)).clicked(&actions) {
384 // delete the item in the data
385 list.redraw(cx);
386 }
387 }
388 }*/
389
390 if let Event::Construct = event {
391 let ironfish = self.audio_graph.by_type::<IronFish>().unwrap();
392 synth_db.nodes = ironfish.settings.live_read();
393 ui.piano(id!(piano)).set_key_focus(cx);
394 self.midi_input = cx.midi_input();
395 }
396
397 if let Event::MidiPorts(ports) = event {
398 cx.use_midi_inputs(&ports.all_inputs());
399 }
400
401 if let Event::AudioDevices(devices) = event {
402 cx.use_audio_outputs(&devices.default_output());
403 }
404
405 ui.radio_button_set(ids!(
406 oscillators.tab1,
407 oscillators.tab2,
408 )).selected_to_visible(cx, &ui, &actions, ids!(
409 oscillators.osc1,
410 oscillators.osc2,
411 ));
412
413 ui.radio_button_set(ids!(
414 filter_modes.tab1,
415 filter_modes.tab2,
416 )).selected_to_visible(cx, &ui, &actions, ids!(
417 preset_pages.tab1_frame,
418 preset_pages.tab2_frame,
419 ));
420
421 ui.radio_button_set(ids!(
422 mobile_modes.tab1,
423 mobile_modes.tab2,
424 mobile_modes.tab3,
425 )).selected_to_visible(cx, &ui, &actions, ids!(
426 application_pages.tab1_frame,
427 application_pages.tab2_frame,
428 application_pages.tab3_frame,
429 ));
430
431 let display_audio = ui.display_audio_set(ids!(display_audio));
432
433 let mut buffers = 0;
434 self.audio_graph.handle_event_with(cx, event, &mut | cx, action | {
435 match action {
436 AudioGraphAction::DisplayAudio {buffer, voice, ..} => {
437 display_audio.process_buffer(cx, None, voice, buffer, 1.0);
438 buffers += 1;
439 }
440 AudioGraphAction::VoiceOff {voice} => {
441 display_audio.voice_off(cx, voice);
442 }
443 };
444 });
445
446 let piano = ui.piano_set(ids!(piano));
447
448 while let Some((_, data)) = self.midi_input.receive() {
449 self.audio_graph.send_midi_data(data);
450 if let Some(note) = data.decode().on_note() {
451 piano.set_note(cx, note.is_on, note.note_number)
452 }
453 }
454
455 for note in piano.notes_played(&actions) {
456 self.audio_graph.send_midi_data(MidiNote {
457 channel: 0,
458 is_on: note.is_on,
459 note_number: note.note_number,
460 velocity: note.velocity
461 }.into());
462 }
463
464 if ui.button_set(ids!(panic)).clicked(&actions) {
465 cx.midi_reset();
466 self.audio_graph.all_notes_off();
467 }
468
469 let sequencer = ui.sequencer(id!(sequencer));
470 // lets fetch and update the tick.
471
472 if ui.button_set(ids!(clear_grid)).clicked(&actions) {
473 sequencer.clear_grid(cx, &mut actions);
474 }
475
476 if ui.button_set(ids!(grid_down)).clicked(&actions) {
477 sequencer.grid_down(cx, &mut actions);
478 }
479
480 if ui.button_set(ids!(grid_up)).clicked(&actions) {
481 sequencer.grid_up(cx, &mut actions);
482 }
483
484 self.data_bind(synth_db.widgets_to_data(cx, &actions, &ui));
485 self.data_bind(synth_db.data_to_widgets(cx, &actions, &ui));
486
487 let ironfish = self.audio_graph.by_type::<IronFish>().unwrap();
488 ironfish.settings.apply_over(cx, &synth_db.nodes);
489 }
490 /*
491 pub fn preset(&mut self, cx: &mut Cx, index: usize, save: bool) {
492 let ironfish = self.audio_graph.by_type::<IronFish>().unwrap();
493 let file_name = format!("preset_{}.txt", index);
494 if save {
495 let nodes = ironfish.settings.live_read();
496 let data = nodes.to_cbor(0).unwrap();
497 let data = makepad_miniz::compress_to_vec(&data, 10);
498 let data = makepad_base64::base64_encode(&data, &makepad_base64::BASE64_URL_SAFE);
499 log!("Saving preset {}", file_name);
500 let mut file = File::create(&file_name).unwrap();
501 file.write_all(&data).unwrap();
502 }
503 else if let Ok(mut file) = File::open(&file_name) {
504 log!("Loading preset {}", file_name);
505 let mut data = Vec::new();
506 file.read_to_end(&mut data).unwrap();
507 if let Ok(data) = makepad_base64::base64_decode(&data) {
508 if let Ok(data) = makepad_miniz::decompress_to_vec(&data) {
509 let mut nodes = Vec::new();
510 nodes.from_cbor(&data).unwrap();
511 ironfish.settings.apply_over(cx, &nodes);
512 //self.imgui.root_frame().bind_read(cx, &nodes);
513 }
514 else {
515 log!("Error decompressing preset");
516 }
517 }
518 else {
519 log!("Error base64 decoding preset");
520 }
521 }
522 }*/
523
524}