From dcdca87f702e75953fcf260f00138e96f6dce9a7 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 9 Jul 2025 15:40:34 -0500 Subject: [PATCH 1/4] add support for ASCII bell escape. Add fruit jam and bell examples --- adafruit_color_terminal.py | 42 +++++++++-- examples/beep.wav | Bin 0 -> 9644 bytes examples/beep.wav.license | 3 + examples/color_terminal_fruit_jam_belltest.py | 71 ++++++++++++++++++ examples/color_terminal_fruit_jam_test.py | 59 +++++++++++++++ ruff.toml | 3 + 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 examples/beep.wav create mode 100644 examples/beep.wav.license create mode 100644 examples/color_terminal_fruit_jam_belltest.py create mode 100644 examples/color_terminal_fruit_jam_test.py diff --git a/adafruit_color_terminal.py b/adafruit_color_terminal.py index 933f249..42701df 100644 --- a/adafruit_color_terminal.py +++ b/adafruit_color_terminal.py @@ -5,7 +5,8 @@ `adafruit_color_terminal` ================================================================================ -Extension of supports ANSI color escapes for subsets of text +Extension of supports ANSI color escapes for subsets of text and optionally the +ASCII bell escape code. * Author(s): Tim Cocks @@ -28,6 +29,7 @@ __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Color_Terminal.git" +from audiocore import WaveFile from displayio import Palette, TileGrid from terminalio import Terminal from tilepalettemapper import TilePaletteMapper @@ -63,9 +65,20 @@ class ColorTerminal: :param height: The height of the terminal in characters. :param custom_palette: A custom palette of colors to use instead of the default ones. Must contain at least 9 colors. + :param audio_interface: The audio interface to use for playing ASCII bell escape codes. + :param bell_audio_file: The wave audio file to use for the ASCII bell escape codes. + Defaults to beep.wav """ - def __init__(self, font, width, height, custom_palette=None): + def __init__( + self, + font, + width, + height, + custom_palette=None, + audio_interface=None, + bell_audio_file="/beep.wav", + ): if custom_palette is None: self.terminal_palette = Palette(9) self.terminal_palette[0] = 0x000000 @@ -99,8 +112,12 @@ def __init__(self, font, width, height, custom_palette=None): self.cur_color_mapping = [0, 1] - @staticmethod - def parse_ansi_colors(text): + self.audio_interface = audio_interface + if audio_interface is not None: + beep_wave_file = open(bell_audio_file, "rb") + self.beep_wave = WaveFile(beep_wave_file) + + def parse_ansi_colors(self, text): """ Parse ANSI color escape codes from a string. @@ -188,13 +205,22 @@ def write(self, s): :param s: The string to write. """ - clean_message, color_map = ColorTerminal.parse_ansi_colors(s) + clean_message, color_map = self.parse_ansi_colors(s) ordered_color_change_indexes = sorted(color_map.keys()) cur_change_index = 0 if not color_map: self.terminal.write(s) + if ( + "\x07" in s + and self.audio_interface is not None + and not self.audio_interface.playing + ): + print("playing beep") + self.audio_interface.play(self.beep_wave) + # while self.audio_interface.playing: + # pass return idx = 0 @@ -222,6 +248,12 @@ def write(self, s): self.apply_color(cur_slice) self.terminal.write(cur_slice) + if ( + "\x07" in cur_slice + and self.audio_interface is not None + and not self.audio_interface.playing + ): + self.audio_interface.play(self.beep_wave) # index after last can be in the color map if color code is last thing in string if idx in color_map: diff --git a/examples/beep.wav b/examples/beep.wav new file mode 100644 index 0000000000000000000000000000000000000000..bdee4e4c10927ca6dd997f19436e4e49effa3897 GIT binary patch literal 9644 zcmaJ`2Y3|K+CDSedrh(_o89ytI*1gNB2q+(bSWZL1tfxkf+9tcE>)z93Q{g8qEzW1 zo%E1oH*GiBB-?wP^Ph3<{2Tv!pMQCFnVI)|@ArQ1`OcZy&6uIDzWPH3025vrKX}f9 zC0Z!}07L&)^+yLQ0lx&Yx=t1x+?W5EI<-LFWpcsE)H`wv^s`LYT`Lhiy;m|%H_QUzdHJSPZ9#eG zxPrB*$K(t!-m*m(DtY56vvjv^7UJJ8pPFjO-`-i5KPL4X6#s$cvaWaOmZt+tZ|PQ| z_|@{xsXyh9>3ldpE%j4*7;dpN>pm(idAg$1tJ{w9&yllI=jBT~&&qeF%#Z`{gC$1) zTdC~Xl~S$#j3olc%8OGvRDT{wCE7!+bBhV^~O?f%*R$=cveu_~>z%*+_-%vKC z*iy@+)V8(83qnojdik$BPFT| zN8NFxZ-NZdmge*=c$nkUj6nJ>TIKblD(XrmRwUQoMDcxP<(geN;rvZG4>Vm-{%6*K z^UL^x}g3+#XF_XDpu4xtr+0RmTMAnKFycs zEYuKE2Ec5G>#@q8OL>*2>IF6o*rmfX`s^3-tFrrRTBQ-FvE8p9RM}iQw6dx`-NpcA zQmy7twl8mgwpjB_O2A&WVEu~9+_JTm+=hNA{;JfLJS+S6ylL4D$$y~u(YCCH%a!k! zU8{Vd;Y}2ORC+x*C;P*^lXFx!^=0yE zDF!~bebvyd%2L+1YFoob8-z=xGm?MFa^(J$H92{XlnHj(E;YPc)xCU4)s2RuHUOqb zyCyHqI-R>PD>Hel6vA^hL&Nc^kIK(h*&8k+eJ@G5$=$OSRc?Y_o@YF|Z4HKS=P z;y)#^B|XeAX5Y#%BpsDR;B0$l)0pc1m6NKwH@%PecS^1$eUot{dws_Fr1dENWBZh* z4b^KZcT~@AT8;RZNwz1A&6u7&GGkrRVido}zNYC>_4Ue<>K#q{>ay`lcZ@uT+h!F*iLy@i`J< z(%$rgSv%8-#8e3e>g-~}x|+|cw${WNEcOVDl~g6pOrMlBDg8+zAK`v`Kf|?}dsVk; zUN;aZJ|I4on3kT9m5}~@qE*ZUV#h*5poaGxuU%${ci^C2{8?g4T4iQ^+Tz4YF#}{f z_8WTD4t@So?J+|)2L}EUk4!v~_HE{ov_Xm2#26UhcxWi9-S~WEZHZx&BLaUFYZ4cw zy_q>TO_}(U7=Wn`pW)Zq2hT6nV#c`+2=|KZ30>0^nK@~ugpDYEi6hHssTEawYP%Re zNBk?rw-P+5B^m9hR}vP9Szx1MyfIrhy1Gy|%eceA0&k1=BwR?{mT@+9L&6lqf84Rg zI9s>9dZBKU@izwnM~LSoEKePiu{d>F!Yg73Z#vExf6zUvKCQcGe1i0K7xzx+mnzNZ zmf9sD3&qzs>Wq3_Oii<{&1i6-`-@nRfTuo652XJHY9s$;Y)bK+pN$gA3^ z9h|mBYf`Nch2U1Fre#B&u6A2pLCa1Q|E}ncYKj(18?F6QHATb%2c6?vuGgjM?$^y| zIpM^>Xwe>(T6;59q1~!_74iS+{JaI$y`y8-Z*2L?8HRmDb5xC*&r>TkZ>#c={!-`J zmOk}A=mytcXlZZ~P%G-KI;QE9dPq~CQi;OQ<*aV`q~54oRo~j;L;2Yvp=zEcl=8L) zs0bkec`lymLVbq*TD{Vwa6xDh>f#DC7g91c#c@qS08(7NP44>H`bhl%Q=SV0#lm0X zEXm7Lnv;*k{UyXeZ`V9iVZ$kX_l6~=fhhip@bkFy$=y>$KsVxHsei;Y8?J~p0j=Xwa}=l54G zPS}?;FQJFBosWT7Pi6aA1NJu<{3IQzhoZc0pKM6yy8-PMdEMq#}r2qKGQSP zJga$_VL|iz<^?GK4*r*l&*FC`E{k8T*nsd)Jlo9&o3|N`HXkvs@-V>1{K<-!_NH2uH@Qkvlaj(WthBYfH-g=oK_LfQ)yHd842jMpFW^13;X)Q0e z?ziqi@pE|-WG7=+sSd}El1<{_;F$M{b$RRlmQ}5PS%2|jU^p*d_FnAEs#&ob*+3M3 z!)vzw+S=H1snutFfa1IH2x&nqu1b$}Npq0CYHy;=(wc7awPx6iUIHfYilw%g8*wc$ zccfBOzsviYEw62sse9X-wg|#;-eKwan3Zv-V)jTqTpS2|AKBh(``%R4w#KIP0bt@T zmVO%ZLfnTjv!!)h05W_(+J0(lHl1$!#a7_Mz*Fu3Y5y2sY>$}U(%T5{?<=)6wPm%o zwbk2R_2J+gH%C7F)m5Bs*-OWQlO)wJ)mpFsHQ+!+$S@>~o{Ia)FV<-hE^Y?qlAwI!JE+VA=z zun#v+QmR-Kb6=4rDM0b1zBcj^m=oLtmWZYqPs; zn>o|rM)fhdrQ!(+vvRcJjyTN00NX#rao60l{gL@ihr|znm2*U#q&TTmDE5eroCwVD zFK`Gfi`u1@m5v-g13cp_6YJ#jmDTcD;%A&7?C(G1c*XKZ`%ud%#~?rY{=yj|J}l2w zekXrXd=ABr^FMKXW(l;fvFII>{TMjHQHkfsjfxp^zIY!e3>W#sj?0!8&DSg<=Z7f& z298OTCqJr4k=Kh>a2Q~sk+Vs3MwYHPD*H1;$XT>c`tlv8SMEU!1riqrw>g4aq-V}91 z@x}i0&NJ5jmUGrS&PqQH(l}j2J!FUFg|bXhJo2Z-Z*rQft1VWm+i6EQkHZiJq|@Y1 zsaJ?290XLZ6x&@(t}WFi2ms(ExHcFnM{t#fUoU8w;aRI+~( zej$A>TOr*gEMueRt-uo3UfT%k5!)(PUlf0X{gH5xbg!(hbb;_ZiXR?0;;ONIX*Jr8 zyCw$+c#J(%$d*o$K}mn%J~j?!1)jNL><_JKd#!5$;@`wh65f}nWH%*J;aWBUR|kTw z33je+s-5fp3gI8JErP9*66x2HdcnIWes`e2z12R#_N~3A`}+WdQ`uJp<0M<9uS?Dd z#-aFgfr;+N_N}&J`+WC>0D7KfZxN^@Bc)Qw8bNP1+K&QXxp|Hfo6xb>{S@(Mvu6sb z#8PRoc$^>w;SGUH?g0*^eT3tN+lutd*_{Q4#SbLkiqi#rR6h|gxK}&I+BZ0?Zf-CF zLo7@%Q@lYkS?uRqSpdWY6`nsGyXd=Zzo(oM3&zKOwp&J}O$sKZ4?C z1P^X;Fj7n1rWxo5j(xRZR{Lg@LMS;pNg_*l4B@Q{0x ziJm7y{e8RK$6fo}BYeX{0Q|x{!5txZQ8-X=kb4~QPYb>8D{;5DYTY0C=7l0~FLMP~ zEC>iVf+FrFChE7)L7&Kz=Z^6l_pJ<}`wR08ZYlpa!4v+=+>e+T*c5u?8|r!AJ;qbv z+k^P$FxA|>{35|Od}$h*ZKLG?43vp7HS7V!`8CUblY4DiF}{Y$+UJS)97{K_x^+Zo+C@AIEy_-S6#o_k?@A)oz zKlCjNEJOV37%SMzxM{q_+z;3*5xyb3H*nk+@}Bkm5ZHtpcu8R}T9>idPOJPqS&A-Bz=f{J$5dRAd7wZOR2KN%D znbnyAKv_5=IMsjCH`CuESdH|hFmAHG4nIG0#T6yFwpGq}$WeLwo&3i`tkau~Z= zLpVCl0M2?A#L@3%ku||`|6qTue@hU3U58eD9*f60#KAakv6>OCjhqe217G?R1DAq% z5dv1=y;+ahQ#rTUU0L^V9Q2IT2S*0}@{bFcgD*z_xPtRpJJ|7@uh|&uEW$@cgrO|~ zc3@XP6`B-bfWx?+IhI|`9>FeQ?nV3yA}@p<1%?EQ1FwcYjNsra{0vjY-pN+7k1$u^ z=vUszf)F0u91sLQ3T;CCAK;t5>}jtbri!cZ0B z&%~XKJk|zQ2Fu7$BmU+{M%WYF8H@xwhwTU#;ddBj=Ig8`<|PIL;TSPG+$~fV>>ZjB z<`4{E!}c*wF@>z3nd=!=ECLgVRpAdpF`;Fljp0-R^*gqJv4HuMIfpreQH&9=JMpjZ z$UjhTyuzrkg%#F+fW+CGu!bcL-VRL9#$Q?3<$D#P2FbTuM7|OIWFvcNt zdz(cFk;3XwK{zh*KFYrdYrrot*v#J;W%wG50X`-AMy7|8!*jz!BI^)dg#Ctp%J_@1 zl<^b(9)^Qm#QeyC@bvHx;iZv71cWoN&+tKvwTu@TALCFrYBvOfOk#{0{Bl!di5-VM~g5KC6>^!y`>xBITBJdqpMXV*}6JHap#2ko$oj{CD!!Xxkz;x7af!F;d;%mA;0 zbQE6$Z^JY29J~l0Llg2x1u{TqG%5!q0rd4Bc%cJYp$mFYZVdT>S^}}+=!kriXViHF zGX6e?e_taY6NHdk5#$IxjzY*!^eYjnocbhrz~5~2IRi!E|3*oKJn#6X##7nI+>{1d z6{U}4@A$_5=AhO8w=onGnT;Gpvr?K!wg1#Wjv}v8>S(ncE1-F(N=e55>l*DG?Ifib z2Vuwt0muPCS<w#vZ#>0QtN9LsGm1L$kqw`L66NOOx zq}mXA)2hj1N*&2ezv#~G*gGU^#}TAYRCb!5^1Y*aD&oIYO3xOViRxdHn;uWCGpaJm zg(yN*M^#2EqFAUopx7u@dKJ(&!* z+@w^Jl}BIE8YoVZH`)!9BheY8_ZnJfG$vXbH9Fdj)cnwOkh2`EiaLv`p^xaf?$|5Q z9h}|;sQ;t&km{%zqY&BwN=tN|k|^CNR1cHRP&H9bQ7%M%Adje>D0+?ZhFS|9=a5<_ zv~y7=ddEPI4}bT2bWM?CI(8g2Pf-U+y;1d)pB=~0S7`U5UQ@eZ$C_weWY5rUQSlx7 zl}4hf=#C>1x@TxKy64jD)GS2Ls0exojPCU02ud~SFEwlQ|1?55PL86xFS-*^eM07^ z)(`DH=}y!Yx|^teAg@z2=o8O|maYE^4Qt=9rG9s-&u+ z*A2Z>(eq4=q5O+FK%J9bQsc;IT5HD{r5x&bf21lPtDiN%Er=A<5J3Mtx zuQAd;@ Date: Wed, 9 Jul 2025 15:42:16 -0500 Subject: [PATCH 2/4] remove commented out code --- adafruit_color_terminal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/adafruit_color_terminal.py b/adafruit_color_terminal.py index 42701df..04ba1a4 100644 --- a/adafruit_color_terminal.py +++ b/adafruit_color_terminal.py @@ -219,8 +219,6 @@ def write(self, s): ): print("playing beep") self.audio_interface.play(self.beep_wave) - # while self.audio_interface.playing: - # pass return idx = 0 From 48cc56aed52171de7a48401eee84d798880bee9b Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 9 Jul 2025 15:43:40 -0500 Subject: [PATCH 3/4] revert unintended staticmethod change --- adafruit_color_terminal.py | 5 +++-- ruff.toml | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_color_terminal.py b/adafruit_color_terminal.py index 04ba1a4..17c3113 100644 --- a/adafruit_color_terminal.py +++ b/adafruit_color_terminal.py @@ -117,7 +117,8 @@ def __init__( beep_wave_file = open(bell_audio_file, "rb") self.beep_wave = WaveFile(beep_wave_file) - def parse_ansi_colors(self, text): + @staticmethod + def parse_ansi_colors(text): """ Parse ANSI color escape codes from a string. @@ -205,7 +206,7 @@ def write(self, s): :param s: The string to write. """ - clean_message, color_map = self.parse_ansi_colors(s) + clean_message, color_map = ColorTerminal.parse_ansi_colors(s) ordered_color_change_indexes = sorted(color_map.keys()) cur_change_index = 0 diff --git a/ruff.toml b/ruff.toml index fd4288d..3c0f2b2 100644 --- a/ruff.toml +++ b/ruff.toml @@ -94,7 +94,6 @@ ignore = [ "PLW1514", # unspecified-encoding "PLR0913", # too many args "PLR0917", # too many positional args - "PLR6301", # method could be function ] From ffc1a996230f4b87d2326c25d209a415015cb8b3 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 9 Jul 2025 16:01:58 -0500 Subject: [PATCH 4/4] fix docs build --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index e38c932..ecd67bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["terminalio", "tilepalettemapper", "displayio"] +autodoc_mock_imports = ["terminalio", "tilepalettemapper", "displayio", "audiocore"] autodoc_preserve_defaults = True