From 93e68a5a009a558e85e02edb9662bed6206cfea7 Mon Sep 17 00:00:00 2001 From: Ben Loftis Date: Tue, 16 Nov 2021 12:06:13 -0600 Subject: [PATCH] MIDI Draw: provide a menu for Channel and Velocity --- gtk2_ardour/editing.h | 4 + gtk2_ardour/editor.cc | 152 ++++++++++++++++++++++++++++++-- gtk2_ardour/editor.h | 36 ++++++-- gtk2_ardour/editor_actions.cc | 101 +++++++++++++++++++++ gtk2_ardour/editor_drag.cc | 4 + gtk2_ardour/editor_mouse.cc | 4 +- gtk2_ardour/midi_region_view.cc | 11 ++- gtk2_ardour/midi_time_axis.cc | 4 + gtk2_ardour/public_editor.h | 3 + 9 files changed, 299 insertions(+), 20 deletions(-) diff --git a/gtk2_ardour/editing.h b/gtk2_ardour/editing.h index 479c52c9f8..4e270dc6b7 100644 --- a/gtk2_ardour/editing.h +++ b/gtk2_ardour/editing.h @@ -51,6 +51,10 @@ enum GridType { #include "editing_syms.h" }; +static const int DRAW_VEL_AUTO = -1; +static const int DRAW_CHAN_AUTO = -1; +static const GridType DRAW_LEN_AUTO = GridTypeNone; //special case: use the Grid's value instead of the note-length selection + extern const char *gridtypestrs[]; inline const char* enum2str(GridType m) {return gridtypestrs[m];} GridType str2gridtype(const std::string &); diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 8268d035af..b551667c8b 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -369,8 +369,10 @@ Editor::Editor () , have_pending_keyboard_selection (false) , pending_keyboard_selection_start (0) , _grid_type (GridTypeBeat) - , _draw_length (GridTypeNone) , _snap_mode (SnapOff) + , _draw_length (GridTypeNone) + , _draw_velocity (-2) + , _draw_channel (-2) , ignore_gui_changes (false) , _drags (new DragManager (this)) , lock_dialog (0) @@ -855,8 +857,11 @@ Editor::Editor () setup_fade_images (); + /* these are defaults; instant.xml will replace these with user's recent selection */ set_grid_to (GridTypeNone); set_draw_length_to (GridTypeBeat); + set_draw_velocity_to (82); + set_draw_channel_to (0); } Editor::~Editor() @@ -2103,6 +2108,18 @@ Editor::draw_length() const return _draw_length; } +int +Editor::draw_velocity() const +{ + return _draw_velocity; +} + +int +Editor::draw_channel() const +{ + return _draw_channel; +} + bool Editor::grid_musical() const { @@ -2201,10 +2218,6 @@ Editor::set_draw_length_to (GridType gt) gt = GridTypeBeat; } - if (_draw_length == gt) { // already set - return; - } - _draw_length = gt; unsigned int grid_index = (unsigned int)gt; @@ -2214,6 +2227,44 @@ Editor::set_draw_length_to (GridType gt) } } +void +Editor::set_draw_velocity_to (int v) +{ + if ( v<0 || v>127 ) { //range-check midi channel + v = DRAW_VEL_AUTO; + } + + _draw_velocity = v; + + if (DRAW_VEL_AUTO==v) { + draw_velocity_selector.set_text (_("Auto")); + return; + } + + char buf[64]; + sprintf(buf, "%d", v ); + draw_velocity_selector.set_text (buf); +} + +void +Editor::set_draw_channel_to (int c) +{ + if ( c<0 || c>15 ) { //range-check midi channel + c = DRAW_CHAN_AUTO; + } + + _draw_channel = c; + + if (DRAW_CHAN_AUTO==c) { + draw_channel_selector.set_text (_("Auto")); + return; + } + + char buf[64]; + sprintf(buf, "%d", c+1 ); + draw_channel_selector.set_text (buf); +} + void Editor::set_grid_to (GridType gt) { @@ -2389,6 +2440,18 @@ Editor::set_state (const XMLNode& node, int version) } set_draw_length_to (draw_length); + int draw_vel; + if (!node.get_property ("draw-velocity", draw_vel)) { + draw_vel = DRAW_VEL_AUTO; + } + set_draw_velocity_to (draw_vel); + + int draw_chan; + if (!node.get_property ("draw-channel", draw_chan)) { + draw_chan = DRAW_CHAN_AUTO; + } + set_draw_channel_to (draw_chan); + SnapMode sm; if (node.get_property ("snap-mode", sm)) { snap_mode_selection_done(sm); @@ -2557,7 +2620,6 @@ Editor::get_state () node->set_property ("zoom", samples_per_pixel); node->set_property ("grid-type", _grid_type); - node->set_property ("draw-length", _draw_length); node->set_property ("snap-mode", _snap_mode); node->set_property ("internal-grid-type", internal_grid_type); node->set_property ("internal-snap-mode", internal_snap_mode); @@ -2566,6 +2628,10 @@ Editor::get_state () node->set_property ("edit-point", _edit_point); node->set_property ("visible-track-count", _visible_track_count); + node->set_property ("draw-length", _draw_length); + node->set_property ("draw-velocity", _draw_velocity); + node->set_property ("draw-channel", _draw_channel); + node->set_property ("playhead", _playhead_cursor->current_sample ()); node->set_property ("left-frame", _leftmost_sample); node->set_property ("y-origin", vertical_adjustment.get_value ()); @@ -3079,6 +3145,8 @@ Editor::setup_toolbar () mouse_mode_size_group->add_widget (grid_type_selector); mouse_mode_size_group->add_widget (draw_length_selector); + mouse_mode_size_group->add_widget (draw_velocity_selector); + mouse_mode_size_group->add_widget (draw_channel_selector); mouse_mode_size_group->add_widget (snap_mode_button); mouse_mode_size_group->add_widget (edit_point_selector); @@ -3190,6 +3258,11 @@ Editor::setup_toolbar () grid_type_selector.set_name ("mouse mode button"); draw_length_selector.set_name ("mouse mode button"); + draw_velocity_selector.set_name ("mouse mode button"); + draw_channel_selector.set_name ("mouse mode button"); + + draw_velocity_selector.disable_scrolling (); + draw_velocity_selector.signal_scroll_event().connect (sigc::mem_fun(*this, &Editor::on_velocity_scroll_event), false); snap_mode_button.set_name ("mouse mode button"); @@ -3214,7 +3287,12 @@ Editor::setup_toolbar () /* Draw - these MIDI tools are only visible when in Draw mode */ draw_box.set_spacing (2); draw_box.set_border_width (2); - draw_box.pack_start (draw_length_selector, false, false); + draw_box.pack_start (*manage (new Label (_("Len:"))), false, false); + draw_box.pack_start (draw_length_selector, false, false, 4); + draw_box.pack_start (*manage (new Label (_("Ch:"))), false, false); + draw_box.pack_start (draw_channel_selector, false, false, 4); + draw_box.pack_start (*manage (new Label (_("Vel:"))), false, false); + draw_box.pack_start (draw_velocity_selector, false, false, 4); /* Pack everything in... */ @@ -3251,6 +3329,25 @@ Editor::setup_toolbar () toolbar_hbox.show_all (); } +bool +Editor::on_velocity_scroll_event (GdkEventScroll* ev) +{ + int v = PBD::atoi (draw_velocity_selector.get_text ()); + switch (ev->direction) { + case GDK_SCROLL_DOWN: + v = std::min (127, v + 1); + break; + case GDK_SCROLL_UP: + v = std::max (1, v - 1); + break; + default: + return false; + } + set_draw_velocity_to(v); + return true; +} + + void Editor::build_edit_point_menu () { @@ -3331,8 +3428,7 @@ Editor::build_grid_type_menu () grid_type_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeCDFrame], sigc::bind (sigc::mem_fun(*this, &Editor::grid_type_selection_done), (GridType) GridTypeCDFrame))); - /* main grid: bars, quarter-notes, etc */ - draw_length_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeBar], sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_selection_done), (GridType) GridTypeBar))); + /* Note-Length when drawing */ draw_length_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeBeat], sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_selection_done), (GridType) GridTypeBeat))); draw_length_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeBeatDiv2], sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_selection_done), (GridType) GridTypeBeatDiv2))); draw_length_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeBeatDiv4], sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_selection_done), (GridType) GridTypeBeatDiv4))); @@ -3340,6 +3436,24 @@ Editor::build_grid_type_menu () draw_length_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeBeatDiv16], sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_selection_done), (GridType) GridTypeBeatDiv16))); draw_length_selector.AddMenuElem (MenuElem (grid_type_strings[(int)GridTypeBeatDiv32], sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_selection_done), (GridType) GridTypeBeatDiv32))); + /* Note-Velocity when drawing */ + { + draw_velocity_selector.AddMenuElem (MenuElem ("8", sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), 8))); + draw_velocity_selector.AddMenuElem (MenuElem ("32", sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), 32))); + draw_velocity_selector.AddMenuElem (MenuElem ("64", sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), 64))); + draw_velocity_selector.AddMenuElem (MenuElem ("82", sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), 82))); + draw_velocity_selector.AddMenuElem (MenuElem ("100", sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), 100))); + draw_velocity_selector.AddMenuElem (MenuElem ("127", sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), 127))); + } + draw_velocity_selector.AddMenuElem (MenuElem (_("Auto"), sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_selection_done), DRAW_VEL_AUTO))); + + /* Note-Channel when drawing */ + for (int i = 0; i<= 15; i++) { + char buf[64]; + sprintf(buf, "%d", i+1); + draw_channel_selector.AddMenuElem (MenuElem (buf, sigc::bind (sigc::mem_fun(*this, &Editor::draw_channel_selection_done), i))); + } + draw_channel_selector.AddMenuElem (MenuElem (_("Auto"), sigc::bind (sigc::mem_fun(*this, &Editor::draw_channel_selection_done), DRAW_CHAN_AUTO))); } void @@ -3365,6 +3479,8 @@ Editor::setup_tooltips () set_tooltip (tav_shrink_button, _("Shrink Tracks")); set_tooltip (visible_tracks_selector, _("Number of visible tracks")); set_tooltip (draw_length_selector, _("Note Length to Draw")); + set_tooltip (draw_velocity_selector, _("Note Velocity to Draw")); + set_tooltip (draw_channel_selector, _("Note Channel to Draw")); set_tooltip (grid_type_selector, _("Grid Mode")); set_tooltip (snap_mode_button, _("Snap Mode\n\nRight-click to visit Snap preferences.")); set_tooltip (edit_point_selector, _("Edit Point")); @@ -3779,6 +3895,24 @@ Editor::draw_length_selection_done (GridType gridtype) } } +void +Editor::draw_velocity_selection_done (int v) +{ + RefPtr ract = draw_velocity_action (v); + if (ract) { + ract->set_active (); + } +} + +void +Editor::draw_channel_selection_done (int c) +{ + RefPtr ract = draw_channel_action (c); + if (ract) { + ract->set_active (); + } +} + void Editor::snap_mode_selection_done (SnapMode mode) { diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 95123cf381..8185845844 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -185,15 +185,23 @@ public: void next_grid_choice (); void prev_grid_choice (); void set_grid_to (Editing::GridType); - void set_draw_length_to (Editing::GridType); void set_snap_mode (Editing::SnapMode); + void set_draw_length_to (Editing::GridType); + void set_draw_velocity_to (int); + void set_draw_channel_to (int); + Editing::SnapMode snap_mode () const; Editing::GridType grid_type () const; - Editing::GridType draw_length () const; bool grid_type_is_musical (Editing::GridType) const; bool grid_musical () const; + bool on_velocity_scroll_event (GdkEventScroll*); + + Editing::GridType draw_length () const; + int draw_velocity () const; + int draw_channel () const; + void undo (uint32_t n = 1); void redo (uint32_t n = 1); @@ -1591,9 +1599,12 @@ private: void move_range_selection_start_or_end_to_region_boundary (bool, bool); Editing::GridType _grid_type; - Editing::GridType _draw_length; Editing::SnapMode _snap_mode; + Editing::GridType _draw_length; + int _draw_velocity; + int _draw_channel; + bool ignore_gui_changes; DragManager* _drags; @@ -1883,9 +1894,12 @@ private: void cycle_edit_mode (); ArdourWidgets::ArdourDropdown grid_type_selector; - ArdourWidgets::ArdourDropdown draw_length_selector; void build_grid_type_menu (); + ArdourWidgets::ArdourDropdown draw_length_selector; + ArdourWidgets::ArdourDropdown draw_velocity_selector; + ArdourWidgets::ArdourDropdown draw_channel_selector; + ArdourWidgets::ArdourButton snap_mode_button; bool snap_mode_button_clicked (GdkEventButton*); @@ -1901,16 +1915,26 @@ private: std::vector snap_mode_strings; void grid_type_selection_done (Editing::GridType); - void draw_length_selection_done (Editing::GridType); void snap_mode_selection_done (Editing::SnapMode); void snap_mode_chosen (Editing::SnapMode); void grid_type_chosen (Editing::GridType); + + void draw_length_selection_done (Editing::GridType); void draw_length_chosen (Editing::GridType); + void draw_velocity_selection_done (int); + void draw_velocity_chosen (int); + + void draw_channel_selection_done (int); + void draw_channel_chosen (int); + Glib::RefPtr grid_type_action (Editing::GridType); - Glib::RefPtr draw_length_action (Editing::GridType); Glib::RefPtr snap_mode_action (Editing::SnapMode); + Glib::RefPtr draw_length_action (Editing::GridType); + Glib::RefPtr draw_velocity_action (int); + Glib::RefPtr draw_channel_action (int); + //zoom focus meu stuff ArdourWidgets::ArdourDropdown zoom_focus_selector; void zoom_focus_selection_done (Editing::ZoomFocus); diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index 13062eacd2..807d61369d 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -622,6 +622,28 @@ Editor::register_actions () ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-beat"), grid_type_strings[(int)GridTypeBeat].c_str(), (sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_chosen), Editing::GridTypeBeat))); ActionManager::register_radio_action (length_actions, draw_length_group, X_("draw-length-bar"), grid_type_strings[(int)GridTypeBar].c_str(), (sigc::bind (sigc::mem_fun(*this, &Editor::draw_length_chosen), Editing::GridTypeBar))); + Glib::RefPtr velocity_actions = ActionManager::create_action_group (bindings, X_("DrawVelocity")); + RadioAction::Group draw_velocity_group; + ActionManager::register_radio_action (velocity_actions, draw_velocity_group, X_("draw-velocity-auto"), _("Auto"), (sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_chosen), DRAW_VEL_AUTO))); + for (int i = 1; i <= 127; i++) { + char buf[64]; + sprintf(buf, X_("draw-velocity-%d"), i); + char vel[64]; + sprintf(vel, X_("%d"), i); + ActionManager::register_radio_action (velocity_actions, draw_velocity_group, buf, vel, (sigc::bind (sigc::mem_fun(*this, &Editor::draw_velocity_chosen), i))); + } + + Glib::RefPtr channel_actions = ActionManager::create_action_group (bindings, X_("DrawChannel")); + RadioAction::Group draw_channel_group; + ActionManager::register_radio_action (channel_actions, draw_channel_group, X_("draw-channel-auto"), _("Auto"), (sigc::bind (sigc::mem_fun(*this, &Editor::draw_channel_chosen), DRAW_CHAN_AUTO))); + for (int i = 0; i <= 15; i++) { + char buf[64]; + sprintf(buf, X_("draw-channel-%d"), i+1); + char ch[64]; + sprintf(ch, X_("%d"), i+1); + ActionManager::register_radio_action (channel_actions, draw_channel_group, buf, ch, (sigc::bind (sigc::mem_fun(*this, &Editor::draw_channel_chosen), i))); + } + Glib::RefPtr snap_actions = ActionManager::create_action_group (bindings, X_("Snap")); RadioAction::Group grid_choice_group; @@ -1083,6 +1105,54 @@ Editor::edit_current_tempo () edit_tempo_section (Temporal::TempoMap::use()->metric_at (ARDOUR_UI::instance()->primary_clock->absolute_time()).get_editable_tempo()); } +RefPtr +Editor::draw_velocity_action (int v) +{ + const char* action = 0; + RefPtr act; + + if (v==DRAW_VEL_AUTO) { + action = "draw-velocity-auto"; + } else if (v>=1 && v<=127) { + char buf[64]; + sprintf(buf, X_("draw-velocity-%d"), v); //we don't allow drawing a velocity 0; some synths use that as note-off + action = buf; + } + + act = ActionManager::get_action (X_("DrawVelocity"), action); + if (act) { + RefPtr ract = RefPtr::cast_dynamic(act); + return ract; + } else { + error << string_compose (_("programming error: %1"), "Editor::draw_velocity_action could not find action to match velocity.") << endmsg; + return RefPtr(); + } +} + +RefPtr +Editor::draw_channel_action (int c) +{ + const char* action = 0; + RefPtr act; + + if (c==DRAW_CHAN_AUTO) { + action = "draw-channel-auto"; + } else if (c>=0 && c<=15) { + char buf[64]; + sprintf(buf, X_("draw-channel-%d"), c+1); + action = buf; + } + + act = ActionManager::get_action (X_("DrawChannel"), action); + if (act) { + RefPtr ract = RefPtr::cast_dynamic(act); + return ract; + } else { + error << string_compose (_("programming error: %1"), "Editor::draw_channel_action could not find action to match channel.") << endmsg; + return RefPtr(); + } +} + RefPtr Editor::draw_length_action (GridType type) { @@ -1353,6 +1423,7 @@ Editor::grid_type_chosen (GridType type) set_grid_to (type); } } + void Editor::draw_length_chosen (GridType type) { @@ -1368,6 +1439,36 @@ Editor::draw_length_chosen (GridType type) } } +void +Editor::draw_velocity_chosen (int v) +{ + /* this is driven by a toggle on a radio group, and so is invoked twice, + once for the item that became inactive and once for the one that became + active. + */ + + RefPtr ract = draw_velocity_action (v); + + if (ract && ract->get_active()) { + set_draw_velocity_to (v); + } +} + +void +Editor::draw_channel_chosen (int c) +{ + /* this is driven by a toggle on a radio group, and so is invoked twice, + once for the item that became inactive and once for the one that became + active. + */ + + RefPtr ract = draw_channel_action (c); + + if (ract && ract->get_active()) { + set_draw_channel_to (c); + } +} + RefPtr Editor::snap_mode_action (SnapMode mode) { diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 0335956fcf..3227077c63 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -7033,6 +7033,8 @@ NoteCreateDrag::finished (GdkEvent* ev, bool had_movement) length = length.round_to_subdivision (div, RoundUpMaybe); } +#warning NUTEMPO ALERT not snapping correctly + _editor->begin_reversible_command (_("Create Note")); _region_view->create_note_at (timepos_t (start), _drag_rect->y0(), length, ev->button.state, false); _editor->commit_reversible_command (); @@ -7116,6 +7118,8 @@ HitCreateDrag::motion (GdkEvent* event, bool) return; } +#warning NUTEMPO ALERT not snapping correctly + _region_view->create_note_at (timepos_t (start), _y, length, event->button.state, false); _last_pos = timepos_t (start); diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index fa720cfdab..98ea551b6f 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -370,9 +370,9 @@ Editor::mouse_mode_toggled (MouseMode m) update_time_selection_display (); if (mouse_mode == MouseDraw) { - draw_length_selector.set_sensitive(true); + draw_box.show(); } else { - draw_length_selector.set_sensitive(false); + draw_box.hide(); } diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index ade5d20bd9..1593a5a9df 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -4214,11 +4214,11 @@ MidiRegionView::get_note_name (boost::shared_ptr n, uint8_t note_value } char buf[128]; - snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d", - (int) note_value, + snprintf (buf, sizeof (buf), "%s #%d\nCh %d Vel %d", name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(), + (int) note_value, (int) n->channel() + 1, - (int) n->velocity()); + (int) n->velocity()); //we display velocity 0-based; velocity 0 is a 'note-off' so the user just sees values 1..127 which 'looks' 1-based return buf; } @@ -4252,6 +4252,11 @@ MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double uint8_t MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const { + PublicEditor& editor = trackview.editor(); + if (editor.draw_velocity() != Editing::DRAW_VEL_AUTO) { + return editor.draw_velocity(); + } + if (_model->notes().empty()) { return 0x40; // No notes, use default } diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 474b7d49a2..e30dc15a4a 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -1726,6 +1726,10 @@ MidiTimeAxisView::stop_step_editing () uint8_t MidiTimeAxisView::get_channel_for_add () const { + if (_editor.draw_channel() != Editing::DRAW_CHAN_AUTO) { + return _editor.draw_channel(); + } + uint16_t const chn_mask = midi_track()->get_playback_channel_mask(); int chn_cnt = 0; uint8_t channel = 0; diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index aa5b347090..10bacba1bd 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -372,6 +372,9 @@ public: virtual Temporal::Beats get_grid_type_as_beats (bool& success, Temporal::timepos_t const & position) = 0; virtual Temporal::Beats get_draw_length_as_beats (bool& success, Temporal::timepos_t const & position) = 0; + virtual int draw_velocity () const = 0; + virtual int draw_channel () const = 0; + virtual unsigned get_grid_beat_divisions (Editing::GridType gt) = 0; virtual int32_t get_grid_music_divisions (Editing::GridType gt, uint32_t event_state) = 0;