From 6f2517f65372805b3c8405d1803cb97b81aa6f75 Mon Sep 17 00:00:00 2001 From: melancholytron Date: Tue, 9 Sep 2025 15:03:06 -0500 Subject: [PATCH] Add Note Limit feature and fix armed system for pattern settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major updates: - Add Note Limit (1-7) to pattern settings - restricts which notes from scale are used - Fix pattern length and note limit to use proper armed system - Both settings now arm (orange) and apply at pattern end without interference - Add spacebar emergency stop (stop-only, doesn't start playback) - Pattern generation respects note limit for all pattern types - Note limit included in preset save/load system - Updated status bar to reflect emergency stop functionality Example: Scale=C major, Note Limit=3, Pattern Length=8 Result: C,D,E,C,D,E,C,D (then pattern repeats) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../arpeggiator_engine.cpython-310.pyc | Bin 25828 -> 27191 bytes core/arpeggiator_engine.py | 82 +++++++++++++++- .../arpeggiator_controls.cpython-310.pyc | Bin 27523 -> 29162 bytes gui/__pycache__/main_window.cpython-310.pyc | Bin 21554 -> 21591 bytes .../preset_controls.cpython-310.pyc | Bin 31973 -> 32090 bytes gui/arpeggiator_controls.py | 88 ++++++++++++++++-- gui/main_window.py | 7 +- gui/preset_controls.py | 6 ++ 8 files changed, 167 insertions(+), 16 deletions(-) diff --git a/core/__pycache__/arpeggiator_engine.cpython-310.pyc b/core/__pycache__/arpeggiator_engine.cpython-310.pyc index 6b12e36018699292f52396be548cb2cb6718a232..ecb5a272117883b77f24de36d1d2d69197d21c85 100644 GIT binary patch delta 7964 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 ckOUv;duTKk}`uGYS`b!E^0&LkPe*^(ds@7{CI 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 List[int]: + """Get scale notes limited by note_limit setting""" + scale_intervals = self.SCALES[self.scale] + notes = [] + + # Calculate the starting note based on root note and scale_note_start + base_octave = self.root_note // 12 + root_in_octave = self.root_note % 12 + + # Get the interval for the selected scale degree + start_degree = self.scale_note_start % len(scale_intervals) + + # Generate only the number of notes specified by note_limit + for i in range(self.note_limit): + degree = (start_degree + i) % len(scale_intervals) + + # If we wrapped around, go to next octave + if i > 0 and degree < (start_degree + i - 1) % len(scale_intervals): + base_octave += 1 + + interval = scale_intervals[degree] + note = base_octave * 12 + root_in_octave + interval + + if 0 <= note <= 127: + notes.append(note) + + return notes + def _get_all_scale_notes(self) -> List[int]: """Get all available scale notes in both directions for patterns that need full range""" + # Use limited scale notes if note_limit is less than full scale + if self.note_limit < 7: + return self._get_limited_scale_notes() + + # Original behavior for full scale (note_limit = 7) up_notes = self._generate_scale_notes_up() down_notes = self._generate_scale_notes_down() @@ -657,14 +701,24 @@ class ArpeggiatorEngine(QObject): def _generate_up_pattern(self) -> List[int]: """Generate ascending arpeggio pattern""" + if self.note_limit < 7: + return self._get_limited_scale_notes() return self._generate_scale_notes_up() def _generate_down_pattern(self) -> List[int]: """Generate descending arpeggio pattern""" + if self.note_limit < 7: + limited_notes = self._get_limited_scale_notes() + return list(reversed(limited_notes)) return self._generate_scale_notes_down() def _generate_up_down_pattern(self) -> List[int]: """Generate up then down pattern""" + if self.note_limit < 7: + limited_notes = self._get_limited_scale_notes() + # Up, then down (avoiding duplicate at starting note) + return limited_notes + list(reversed(limited_notes))[1:] + up_notes = self._generate_scale_notes_up() down_notes = self._generate_scale_notes_down() # Up, then down (avoiding duplicate at starting note) @@ -672,6 +726,11 @@ class ArpeggiatorEngine(QObject): def _generate_down_up_pattern(self) -> List[int]: """Generate down then up pattern""" + if self.note_limit < 7: + limited_notes = self._get_limited_scale_notes() + # Down, then up (avoiding duplicate at starting note) + return list(reversed(limited_notes)) + limited_notes[1:] + down_notes = self._generate_scale_notes_down() up_notes = self._generate_scale_notes_up() # Down, then up (avoiding duplicate at starting note) @@ -940,6 +999,19 @@ class ArpeggiatorEngine(QObject): self.armed_scale_note_start = None changes_applied = True + # Apply armed pattern length + if self.armed_pattern_length is not None: + self.user_pattern_length = self.armed_pattern_length + self.volume_engine.set_bar_length(self.armed_pattern_length) + self.armed_pattern_length = None + changes_applied = True + + # Apply armed note limit + if self.armed_note_limit is not None: + self.note_limit = self.armed_note_limit + self.armed_note_limit = None + changes_applied = True + # Apply armed preset if self.armed_preset_data is not None and self.preset_apply_callback: try: diff --git a/gui/__pycache__/arpeggiator_controls.cpython-310.pyc b/gui/__pycache__/arpeggiator_controls.cpython-310.pyc index db87535f5936afca50b2cc81c327d0d3277e390e..0f89ca425007fce1aa6978afa187dc1314c1abaa 100644 GIT binary patch delta 8138 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 Mkx_i}w;&rv09TMikN^Mx delta 172 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 delta 4568 zcma)93s6+&6~5?8OY!Osao z1bGBQ1iwHC$Gd&q+eJP-KS}UQf~N?cCfHB#D}rZaUr`-wl81|G%FW5@HFJvkvDvC# zHQUsW%y#vPDI*EuZ z@VMSpTn|BIOI&DdU`$BWvbCfkbqGMg?ZV-6J2Mj^Kphf0WU zRy6F|c*Q?yH*7x&%3vxe18M%s(vTydXjNLZzbugY5ImvmQ-?6VW@Q~J45mJiN@@nx z5B-XxMw%R#gA0vLZ^3@zb{~kVtf=Om-(1DNzuqmLrNv0|i|6Q(+CFUR@w@z9cTD>` z)XjT-O23nLix)_ij)OzJREqyy3KGLO8b}SuU1gKNDSub?ThkF*IjV1S?gVq1m_<*A zG?~=E>A~2h%jR+U@tJ4kv z9FR{|Pd7b}jBtiru_QxYtMCt z5UeD417Q?DvSC&cRO!B1&p|qcJdnK7)8*>)D}`!EEt;gtc~g5K^9uiNF1L`$CVoTEE3Y5Tt;K1hmG364d4PE%g})s2KE?6k~LjVPJX zwPR@s$CM({2+onunEvfl;uPg+y?lNjSU4sWR79dYxS+(uEd%oH1=&F>>2vo)q7f;? z<5r_!!ehFHx641WdBSGeA8r;tL>vxB!Y668aHgT97xBg0LkGpG#)Dl=0z2q^^%iJ& zpF-o2!H1b$pC=wSL>$+oC8^CTdPaz_qSftGs zx4ROTiANLMsA#-#-2P1rcE}ZtX{I-E^#e*M3kg+C#e|Y&bK`!lK%DDJGtsF1 zQkB(98sR;;bIBUKPQJC|yB0nL8_9f;^e)YXc=`RMKejX1MO+&0xHM&1E+opO%l1PN zSsT-iY`aWZuA9g?3J}waWQP3v@_FbXe?{@gr5aqy*0mM=09o?%y))pVOkY`y#_n8s zJgbZf+m8}0DLtMJkK6Cz-R>DS`$JrrraPOON%x^u^I^I?vFb!BYZc*|Lp3((f()(B zgt_v_>Q_z1;g%1sai(5Gk!OYrXS`YD@im#yAm3W^X09QJ5`ruVyrC(gih#64HKmob z1Ibl*Tt$t%)Vwbvo0ccvE@{%!!n?#jLr+)_EPh9+8WRx1!-% zj@CvLfk(f*?kRv$Der5`h~z4YDX!_{{iL+6&w)4O)Pi-BUyLjU*w0!5HG8W zLSHUoZX}_3M9n9-JxNiIysxOa7RpoJ`nG5qw`U;kA>D5*iV+x!+?r5}NxXr8_cNN^ z4=}E4a{HE$Q6t+Nl(AyKVlFNW6iTZvOFCVNdh1pIsFSXa4Y{LjGIs%Y%U$wJM;WB+ z=FSFynX;*?ZhkZg7URCyL;Ep_fX&e+JB;Dh61k)H_&wd?Fv-l~c`_5peEq{Nr)s>n z6J+kTRn@;HfddHPc>nh9;hGi^6H~!YS4x_e#fbNc+`r9NIF44LZx zGY-mGq9B-0as>p11TillzL!|MU2Q(miC+W0F5{IHy$+a-OG;9w3-b}JnpcQB$PQfy z!j@ZHm)|AW(SKbLWEgK9GxYbwQ5BZRq26p*Dv$NHTN260N`hswvTsS&3L+;^qW2O- zv)|Z(O8Hn{KQu{Of7Q$+TH-5d%=J5(72hY0k#?AXC)+_e(7zh0Fc-!4itYvUTvi8jlb_b;AsjvR4gMozofg}kl$p8GdJIZxOeGQqQF zB2g)%Lc=rm);xn-Wi76}D(eOw active + if self.armed_pattern_length_button and hasattr(self.arpeggiator, 'armed_pattern_length') and self.arpeggiator.armed_pattern_length is None: + for length, btn in self.pattern_length_buttons.items(): + if btn == self.armed_pattern_length_button: + # Clear old active pattern length + if self.current_pattern_length in self.pattern_length_buttons: + self.apply_button_style(self.pattern_length_buttons[self.current_pattern_length], 12, "normal") + # Set new active pattern length (orange -> orange) + self.current_pattern_length = length + self.apply_button_style(btn, 12, "orange") + self.armed_pattern_length_button = None + break + + # Note limit armed -> active + if self.armed_note_limit_button and hasattr(self.arpeggiator, 'armed_note_limit') and self.arpeggiator.armed_note_limit is None: + for limit, btn in self.note_limit_buttons.items(): + if btn == self.armed_note_limit_button: + # Clear old active note limit + if self.current_note_limit in self.note_limit_buttons: + self.apply_button_style(self.note_limit_buttons[self.current_note_limit], 12, "normal") + # Set new active note limit (orange -> blue) + self.current_note_limit = limit + self.apply_button_style(btn, 12, "blue") + self.armed_note_limit_button = None + break + # Speed changes apply immediately - no armed state needed def update_gui_from_engine(self): @@ -1011,12 +1074,23 @@ class ArpeggiatorControls(QWidget): if hasattr(self, 'pattern_length_buttons'): # Clear current pattern length styling if hasattr(self, 'current_pattern_length') and self.current_pattern_length in self.pattern_length_buttons: - self.pattern_length_buttons[self.current_pattern_length].setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;") + self.apply_button_style(self.pattern_length_buttons[self.current_pattern_length], 12, "normal") # Set new active pattern length if hasattr(self.arpeggiator, 'user_pattern_length'): self.current_pattern_length = self.arpeggiator.user_pattern_length if self.current_pattern_length in self.pattern_length_buttons: - self.pattern_length_buttons[self.current_pattern_length].setStyleSheet("background: #cc6600; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ee8800;") + self.apply_button_style(self.pattern_length_buttons[self.current_pattern_length], 12, "orange") + + # Update note limit buttons + if hasattr(self, 'note_limit_buttons'): + # Clear current note limit styling + if hasattr(self, 'current_note_limit') and self.current_note_limit in self.note_limit_buttons: + self.apply_button_style(self.note_limit_buttons[self.current_note_limit], 12, "normal") + # Set new active note limit + if hasattr(self.arpeggiator, 'note_limit'): + self.current_note_limit = self.arpeggiator.note_limit + if self.current_note_limit in self.note_limit_buttons: + self.apply_button_style(self.note_limit_buttons[self.current_note_limit], 12, "blue") # Update delay controls if hasattr(self, 'delay_enabled_checkbox'): diff --git a/gui/main_window.py b/gui/main_window.py index 149e5cf..7ca2559 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -172,7 +172,7 @@ class MainWindow(QMainWindow): main_layout.addWidget(status_frame) # Create status bar - self.statusBar().showMessage("Ready - Use keyboard (AWSDFGTGHYUJ) to play notes, SPACE to start/stop") + self.statusBar().showMessage("Ready - Use keyboard (AWSDFGTGHYUJ) to play notes, SPACE for emergency stop") # Create menu bar self.create_menu_bar() @@ -664,11 +664,10 @@ class MainWindow(QMainWindow): self.statusBar().showMessage(f"Note ON: {note}", 500) elif key == Qt.Key_Space: - # Spacebar starts/stops arpeggiator + # Spacebar emergency stop only (does not start playback) if self.arpeggiator.is_playing: self.on_stop_clicked() - else: - self.on_play_clicked() + self.statusBar().showMessage("Emergency stop activated", 1000) super().keyPressEvent(event) diff --git a/gui/preset_controls.py b/gui/preset_controls.py index 3c7ca31..fe7110a 100644 --- a/gui/preset_controls.py +++ b/gui/preset_controls.py @@ -333,6 +333,7 @@ class PresetControls(QWidget): "velocity": self.arpeggiator.velocity, "tempo": self.arpeggiator.tempo, "user_pattern_length": getattr(self.arpeggiator, 'user_pattern_length', 8), + "note_limit": getattr(self.arpeggiator, 'note_limit', 7), "channel_distribution": self.arpeggiator.channel_distribution, "delay_enabled": self.arpeggiator.delay_enabled, "delay_length": self.arpeggiator.delay_length, @@ -389,6 +390,11 @@ class PresetControls(QWidget): elif hasattr(self.arpeggiator, 'set_pattern_length'): self.arpeggiator.set_pattern_length(pattern_length) + # Apply note limit + note_limit = arp_settings.get("note_limit", 7) + if hasattr(self.arpeggiator, 'set_note_limit'): + self.arpeggiator.set_note_limit(note_limit) + # Apply channel distribution self.arpeggiator.set_channel_distribution(arp_settings.get("channel_distribution", "up"))