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}