circuitpython-keymanager 1.1.1__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/PKG-INFO +1 -1
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/circuitpython_keymanager.egg-info/PKG-INFO +1 -1
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/pyproject.toml +1 -1
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/relic_keymanager.py +113 -10
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/build.yml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/failure-help-text.yml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/release_gh.yml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/release_pypi.yml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.gitignore +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.pre-commit-config.yaml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.readthedocs.yaml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/CODE_OF_CONDUCT.md +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/LICENSE +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/LICENSES/MIT.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/LICENSES/Unlicense.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/README.rst +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/README.rst.license +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/circuitpython_keymanager.egg-info/SOURCES.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/circuitpython_keymanager.egg-info/dependency_links.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/circuitpython_keymanager.egg-info/requires.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/circuitpython_keymanager.egg-info/top_level.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/_static/favicon.ico +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/_static/favicon.ico.license +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/api.rst +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/api.rst.license +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/conf.py +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/examples.rst +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/examples.rst.license +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/index.rst +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/index.rst.license +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/requirements.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_arpeggiator.py +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_keys.py +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_sequencer.py +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_simpletest.py +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_synthio.py +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/optional_requirements.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/requirements.txt +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/ruff.toml +0 -0
- {circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-keymanager
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Tools to manage notes in musical applications. Includes note priority, arpeggiation, and sequencing.
|
|
5
5
|
Author-email: Cooper Dalrymple <me@dcdalrymple.com>
|
|
6
6
|
License: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: circuitpython-keymanager
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Tools to manage notes in musical applications. Includes note priority, arpeggiation, and sequencing.
|
|
5
5
|
Author-email: Cooper Dalrymple <me@dcdalrymple.com>
|
|
6
6
|
License: MIT
|
|
@@ -13,7 +13,7 @@ requires = [
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "circuitpython-keymanager"
|
|
15
15
|
description = "Tools to manage notes in musical applications. Includes note priority, arpeggiation, and sequencing."
|
|
16
|
-
version = "1.
|
|
16
|
+
version = "1.2.0"
|
|
17
17
|
readme = "README.rst"
|
|
18
18
|
authors = [
|
|
19
19
|
{name = "Cooper Dalrymple", email = "me@dcdalrymple.com"}
|
|
@@ -24,7 +24,7 @@ Implementation Notes
|
|
|
24
24
|
|
|
25
25
|
# imports
|
|
26
26
|
|
|
27
|
-
__version__ = "1.
|
|
27
|
+
__version__ = "1.2.0"
|
|
28
28
|
__repo__ = "https://github.com/relic-se/CircuitPython_KeyManager.git"
|
|
29
29
|
|
|
30
30
|
import asyncio
|
|
@@ -331,10 +331,26 @@ class Timer:
|
|
|
331
331
|
|
|
332
332
|
_last_press: list[int] = []
|
|
333
333
|
|
|
334
|
-
|
|
334
|
+
def update(self) -> None:
|
|
335
335
|
"""Update the timer object and call any relevant callbacks if a new beat step or the end of
|
|
336
|
-
the gate of a step is reached.
|
|
337
|
-
child class that utilizes the
|
|
336
|
+
the gate of a step is reached. For best performance, call this method frequently! The
|
|
337
|
+
actual functionality of this method will depend on the child class that utilizes the
|
|
338
|
+
:class:`Timer` parent class.
|
|
339
|
+
"""
|
|
340
|
+
if not self._active:
|
|
341
|
+
return
|
|
342
|
+
current = time.monotonic()
|
|
343
|
+
if self._last_press and current - self._now >= self._gate_duration:
|
|
344
|
+
self._do_release()
|
|
345
|
+
if current - self._now >= self._step_time:
|
|
346
|
+
self._update()
|
|
347
|
+
self._do_step()
|
|
348
|
+
self._now += self._step_time
|
|
349
|
+
|
|
350
|
+
async def update_async(self):
|
|
351
|
+
"""Update the timer object using asyncio and call any relevant callbacks if a new beat step
|
|
352
|
+
or the end of the gate of a step is reached. The actual functionality of this method will
|
|
353
|
+
depend on the child class that utilizes the :class:`Timer` parent class.
|
|
338
354
|
"""
|
|
339
355
|
while True:
|
|
340
356
|
if not self._active:
|
|
@@ -534,6 +550,16 @@ class Arpeggiator(Timer):
|
|
|
534
550
|
self._do_press(self._notes[self._pos].notenum, self._notes[self._pos].velocity)
|
|
535
551
|
|
|
536
552
|
|
|
553
|
+
class LoopType:
|
|
554
|
+
"""An enum-like class representing sequencer looping methods."""
|
|
555
|
+
|
|
556
|
+
LOOP: int = const(0)
|
|
557
|
+
"""When the end of a loop is reached, return back to the start."""
|
|
558
|
+
|
|
559
|
+
SINGLE: int = const(1)
|
|
560
|
+
"""When the end of a loop is reached, stop sequence."""
|
|
561
|
+
|
|
562
|
+
|
|
537
563
|
class Sequencer(Timer):
|
|
538
564
|
"""Sequence notes using the :class:`Timer` class to create a multi-track note sequencer. By
|
|
539
565
|
default, the Sequencer is set up for a single 4/4 measure of 16 notes with one track. Each note
|
|
@@ -551,6 +577,9 @@ class Sequencer(Timer):
|
|
|
551
577
|
self.tracks = tracks
|
|
552
578
|
self._data = [[None for j in range(self._length)] for i in range(self._tracks)]
|
|
553
579
|
|
|
580
|
+
def _get_end(self) -> int:
|
|
581
|
+
return min(self._loop_end, self._length) if self._loop_end is not None else self._length
|
|
582
|
+
|
|
554
583
|
_data: list = None
|
|
555
584
|
|
|
556
585
|
_length: int = 16
|
|
@@ -558,9 +587,10 @@ class Sequencer(Timer):
|
|
|
558
587
|
@property
|
|
559
588
|
def length(self) -> int:
|
|
560
589
|
"""The number of steps for each track. If the length is shortened, all of the step data
|
|
561
|
-
beyond the new length will be deleted
|
|
562
|
-
|
|
563
|
-
is
|
|
590
|
+
beyond the new length will be deleted and the :attr:`loop_start` and :attr:`loop_end`
|
|
591
|
+
properties may be altered. If the sequencer is currently running, it should loop back around
|
|
592
|
+
automatically to the start of the track data if :attr:`position` is beyond the new value.
|
|
593
|
+
The minimum length allowed is 1.
|
|
564
594
|
"""
|
|
565
595
|
return self._length
|
|
566
596
|
|
|
@@ -575,6 +605,9 @@ class Sequencer(Timer):
|
|
|
575
605
|
for i in range(self._tracks):
|
|
576
606
|
del self._data[i][value:]
|
|
577
607
|
self._length = value
|
|
608
|
+
if self._loop_end is not None:
|
|
609
|
+
self._loop_end = min(self._loop_end, self._length)
|
|
610
|
+
self._loop_start = min(self._loop_start, self._get_end() - 1)
|
|
578
611
|
|
|
579
612
|
_tracks: int = 1
|
|
580
613
|
|
|
@@ -598,6 +631,47 @@ class Sequencer(Timer):
|
|
|
598
631
|
del self._data[value:]
|
|
599
632
|
self._tracks = value
|
|
600
633
|
|
|
634
|
+
_loop_start: int = 0
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def loop_start(self) -> int:
|
|
638
|
+
"""The index of the sequence of which to begin at when looping back. This occurs when
|
|
639
|
+
:attr:`position` reaches :attr:`loop_end`. Should be a value between 0 and :attr:`loop_end`
|
|
640
|
+
- 1.
|
|
641
|
+
"""
|
|
642
|
+
return self._loop_start
|
|
643
|
+
|
|
644
|
+
@loop_start.setter
|
|
645
|
+
def loop_start(self, value: int) -> int:
|
|
646
|
+
self._loop_start = min(max(value, 0), self._get_end() - 1)
|
|
647
|
+
|
|
648
|
+
_loop_end: int | None = None
|
|
649
|
+
|
|
650
|
+
@property
|
|
651
|
+
def loop_end(self) -> int | None:
|
|
652
|
+
"""The index of the sequence of which to loop back from when :attr:`position` reaches it.
|
|
653
|
+
Should be a value between :attr:`loop_start` + 1 and :attr:`length`. If set as `None`, this
|
|
654
|
+
value will be ignored and :attr:`length` will be used instead.
|
|
655
|
+
"""
|
|
656
|
+
return self._loop_end
|
|
657
|
+
|
|
658
|
+
@loop_end.setter
|
|
659
|
+
def loop_end(self, value: int | None) -> None:
|
|
660
|
+
self._loop_end = (
|
|
661
|
+
min(max(value, self._loop_start + 1), self._length) if value is not None else value
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
_loop_type: int = LoopType.LOOP
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def loop_type(self) -> int:
|
|
668
|
+
"""The method of looping through the sequence. See :class:`LoopType` for options."""
|
|
669
|
+
return self._mode
|
|
670
|
+
|
|
671
|
+
@loop_type.setter
|
|
672
|
+
def loop_type(self, value: int) -> None:
|
|
673
|
+
self._loop_type = value % 2
|
|
674
|
+
|
|
601
675
|
_pos: int = 0
|
|
602
676
|
|
|
603
677
|
@property
|
|
@@ -605,6 +679,12 @@ class Sequencer(Timer):
|
|
|
605
679
|
"""The current position of the sequencer within the track length (0-based)."""
|
|
606
680
|
return self._pos
|
|
607
681
|
|
|
682
|
+
@position.setter
|
|
683
|
+
def position(self, value: int) -> None:
|
|
684
|
+
self._pos = value % self._length
|
|
685
|
+
if self._last_press:
|
|
686
|
+
self._do_release()
|
|
687
|
+
|
|
608
688
|
def set_note(self, position: int, notenum: int, velocity: float = 1.0, track: int = 0) -> None:
|
|
609
689
|
"""Set the note value and velocity of a track at a specific step index.
|
|
610
690
|
|
|
@@ -663,7 +743,13 @@ class Sequencer(Timer):
|
|
|
663
743
|
"""
|
|
664
744
|
|
|
665
745
|
def _update(self):
|
|
666
|
-
self._pos
|
|
746
|
+
self._pos += 1
|
|
747
|
+
end = self._get_end()
|
|
748
|
+
if self._pos >= end:
|
|
749
|
+
self._pos = min(max(self._loop_start, 0), end - 1)
|
|
750
|
+
if self._loop_type == LoopType.SINGLE:
|
|
751
|
+
self.active = False
|
|
752
|
+
return
|
|
667
753
|
for i in range(self._tracks):
|
|
668
754
|
note = self._data[i][self._pos]
|
|
669
755
|
if note and note[0] > 0 and note[1] > 0:
|
|
@@ -843,8 +929,25 @@ class Keyboard:
|
|
|
843
929
|
self._sustained = [note for note in self._sustained if note != notenum]
|
|
844
930
|
self._update()
|
|
845
931
|
|
|
846
|
-
|
|
847
|
-
"""Update :attr:`keys` objects if they were provided during initialization.
|
|
932
|
+
def update(self) -> None:
|
|
933
|
+
"""Update :attr:`keys` objects if they were provided during initialization. For best
|
|
934
|
+
performance, call frequently!
|
|
935
|
+
|
|
936
|
+
:param delay: The amount of time to sleep between polling in seconds.
|
|
937
|
+
"""
|
|
938
|
+
if event := self._keys.events.get():
|
|
939
|
+
notenum = self.root + event.key_number
|
|
940
|
+
if event.pressed:
|
|
941
|
+
self.append(notenum, keynum=event.key_number)
|
|
942
|
+
if callable(self.on_key_press):
|
|
943
|
+
self.on_key_press(event.key_number, notenum, 1.0)
|
|
944
|
+
elif event.released:
|
|
945
|
+
self.remove(notenum)
|
|
946
|
+
if callable(self.on_key_release):
|
|
947
|
+
self.on_key_release(event.key_number, notenum)
|
|
948
|
+
|
|
949
|
+
async def update_async(self, delay: float = 0.01) -> None:
|
|
950
|
+
"""Update :attr:`keys` objects if they were provided during initialization using asyncio.
|
|
848
951
|
|
|
849
952
|
:param delay: The amount of time to sleep between polling in seconds.
|
|
850
953
|
"""
|
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/build.yml
RENAMED
|
File without changes
|
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/release_gh.yml
RENAMED
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/.github/workflows/release_pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/docs/_static/favicon.ico.license
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_arpeggiator.py
RENAMED
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_keys.py
RENAMED
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_sequencer.py
RENAMED
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_simpletest.py
RENAMED
|
File without changes
|
{circuitpython_keymanager-1.1.1 → circuitpython_keymanager-1.2.0}/examples/keymanager_synthio.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|