diff --git a/README.md b/README.md
index d1b0738..ca7377b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
# Spectrogram
diff --git a/hardware/pcb/~Spectrogram flex pcb.kicad_sch.lck b/hardware/pcb/~Spectrogram flex pcb.kicad_sch.lck
deleted file mode 100644
index 6906f88..0000000
--- a/hardware/pcb/~Spectrogram flex pcb.kicad_sch.lck
+++ /dev/null
@@ -1 +0,0 @@
-{"hostname":"TAPLOP","username":"hankm"}
\ No newline at end of file
diff --git a/software/micropython/colour_lut_builder.py b/software/micropython/colour_lut_builder.py
new file mode 100644
index 0000000..1a97f2b
--- /dev/null
+++ b/software/micropython/colour_lut_builder.py
@@ -0,0 +1,51 @@
+#This was the Claude sonnet 4.5 builder for the LUT
+def create_color_lut():
+ lut = []
+ for i in range(256):
+ if i <= 170:
+ progress = i / 170.0
+ r = int(255 * progress)
+ g = 0
+ b = int(255 * (1 - progress))
+ else:
+ progress = (i - 171) / 84.0
+ r = 255
+ g = int(255 * progress)
+ b = 0
+ lut.append((r, g, b))
+ return lut
+
+#this is astrolabe's generalised LUT builder
+#color_list must be same length as colour_stop_positions. positions must be ascending order. range top must be = top position
+def generalised_color_lut(color_list, color_stop_positions, range_top_value, step_size):
+ lut = []
+ color_pairs = []
+ stop_pairs = []
+
+ for i in range(len(color_list)-1):
+ color_pairs.append([color_list[i],color_list[i+1]])
+ stop_pairs.append([color_stop_positions[i],color_stop_positions[i+1]])
+# print("colour_pairs",color_pairs)
+# print("stop_pairs",stop_pairs)
+
+ start_val=0
+ for index, pair in enumerate(color_pairs):
+# progress=#0-1 value
+# print(index, pair)
+ for i in range(start_val,range_top_value,step_size): #I have constructed this to work with 255 color values. For LUTS that step at 1 unit oer step, it looks algood.
+ #for LUTS that step at greater steps, more thought is required when calling them: e.g., calling with step_size=256/4 will not perfectly map from start to end (the last colour step will be missing) Therefor, call with correspondingly coarser steps. (n-1)
+ if i<=stop_pairs[index][1]:
+ progress= (i-stop_pairs[index][0])/(stop_pairs[index][1]-stop_pairs[index][0]) #progress is a 0-1 parameterized value within each stop range
+# print(progress)
+ #base value + delta (can be +ve or -ve) * progress fraction of delta's application across range
+ r=int(pair[0][0]+((pair[1][0]-pair[0][0])*progress))
+ g=int(pair[0][1]+((pair[1][1]-pair[0][1])*progress))
+ b=int(pair[0][2]+((pair[1][2]-pair[0][2])*progress))
+# print(r,g,b)
+ lut.append((r, g, b))
+ else:
+# print('next colour transition/stop range')
+ start_val=i
+ break #break out so the start_val isn't continuously updated as the loops runs out normally
+#
+ return lut
\ No newline at end of file
diff --git a/software/micropython/config.py b/software/micropython/config.py
new file mode 100644
index 0000000..096ace1
--- /dev/null
+++ b/software/micropython/config.py
@@ -0,0 +1,118 @@
+TEST='test'
+
+FLIP_DISPLAY=True #NOT IMPLIMENTED, LOL #True: high freqencies on left, low on the right - False: vice versa
+
+#presets:
+size='s' #(s,m,l,c) #small 7px, medium 12px, large 18/24px, c=custom, go wild
+color_blind=False #True/False
+
+if size=='s':
+ NUM_LEDS = 7
+ #'Resolution'=notes_per_LED: [1,2,3,4,6,12]
+ BOOT_RESOLUTION_INDEX=5
+
+if size=='m':
+ #goal: make this arbitrarily small/long & accurately reflected in setting. No funny +1 business.
+ NUM_LEDS = 12 #50 #actually needs to be number of leds+1# ugly work around for array out of bound error caused by ring buffer in mic.py(??)
+ #'Resolution'=notes_per_LED: [1,2,3,4,6,12]
+ BOOT_RESOLUTION_INDEX=4
+
+
+#configure touch thresholds:
+UNPRESSED_CAPACITIVE_READING=80000
+PRESSED_CAPACITIVE_READING=90000
+#print out the touch levels and compare to the below thresholds.
+PRINT_TOUCH_READING=True
+
+#these are the hues the maker selected for the 12 notes of an octave, you can change them :)
+import colour_lut_builder as clb
+if color_blind==True:
+ #viridis for replacing intensity for color blind folks:
+ INTENSITY_COLOR_LUT=clb.generalised_color_lut([(0,0,0),(0,0,255),(0,255,0),(255,255,0)],[0,63,191,255],256,1)
+ #plasma note scheme
+ SYN_NOTE_HUES=clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0),(255,255,255)],[0,63,211,255],256,22)
+
+ #Bunch of tests: you can go crazy trying to pick colours, particularly when you can't 'see' them.
+ #scheme build call for plasma synesthesia
+ #plasma0: missing the white top end, which will allow better determination
+ #clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0)],[0,170,255],256,20)
+ #plasma1: missing the white top end, which will allow better determination. White further fits in with the perceptual brightness angle of these colour schemes
+ #clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0),(255,255,255)],[0,63,191,255],256,20)
+ #plasma2: yellows not distinct: moving stops. Yellow ususally a small end band
+ #clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0),(255,255,255)],[0,63,211,255],256,22)
+ #plasma3 adding green denuemont to white, to move closer to blue/a cyclical clour scheme
+ #clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0),(255,255,255),(0,255,0)],[0,60,120,180,255],256,22)
+ #plasma4 still messing
+ #clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0),(255,255,255),(0,255,0),(0,255,255)],[0,60,120,150,200,255],256,22)
+ #plasma5: just adding sharps manually. 256/7=36.57
+ #clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0),(255,255,255)],[0,63,211,255],256,36)
+ #which yeilds: [(0, 0, 255), (145, 0, 109), (255, 15, 0), (255, 77, 0), (255, 139, 0), (255, 201, 0), (255, 255, 28), (255, 255, 237)]
+
+else:
+ #plama scheme
+ INTENSITY_COLOR_LUT=clb.generalised_color_lut([(0,0,255),(255,0,0),(255,255,0)],[0,170,255],256,1)
+ #rainbow note scheme - manually selected
+ SYN_NOTE_HUES=[(255,0,0),(255,30,30),(255,60,0),(255,255,0),(255,255,30),(0,255,0),(80,220,10),(0,155,255),(0,0,255),(50,0,255),(255,0,255),(255,255,255)]
+
+DEV_STATUS_LED_PIN=21
+
+#pipedream
+HALVED_MIRRORED_SPECTRUM=False
+MIRROR_START_INDEX=NUM_LEDS/2
+
+#Colour selections:
+
+
+#this can be used to change the direction of the waterfall. Depends on one's prefered viewing angle Normally pin0=6,pin1=8,pin2=7
+LEDS_PIN0 = 6
+LEDS_PIN1 = 8
+LEDS_PIN2 = 7
+
+ID = 0 #I2S identity
+#mic pins
+SD = 11
+SCK = 10
+WS = 9
+
+#Boot visualisation settings/options:
+
+#integer multiples of 10: logic/resolution is set in menu.py
+BOOT_MAX_DB=-40
+BOOT_MIN_DB=-80
+
+
+#menu display stuff
+MENU_LED_OFFSET=0 #useful for (e.g.) 18 or 24 wide menus that only want 12 pixel wide displays
+MENU_SIZE=NUM_LEDS-MENU_LED_OFFSET-1 #or face crash #counting from zero... LEDS are indexed from 0... #ideally should be tied to LEDS-1
+MENU_SCALE=1 #not used?? useful for crunching down the menu's size (1 pixel=?, 6pixels=0.5, 12pixels=1
+
+BOOT_BRIGHTNESS_INDEX=1
+BRIGHTNESS_OPTIONS=[2,6,16,32,64,128,255] #this is semi-independent of the menu width, to make it line up, have the same number of entries as the menu size. If you go outside the menu size, the top brightness pixel isn't displayed.
+#two hours of manual fitting by eye and this was the screamingly obvious final outcome: for 7 pixels=[4,8,16,32,64,128,255] I modified the low blues to get that "blue LED not on anymore" vibe
+#[2,7,28,64,113,177,255] for 7 pixels, calculated as a square relationship increase in brightness (doesn't look good though lol)
+#for 12 pixels, i guessed/input:[2,3,4,5,7,10,20,35,50,90,160,255]
+
+DB_RANGE=120
+DB_STEP_SIZE=5#[10db, 5db, 1db]
+from math import ceil
+DB_SETTINGS_PER_BIN=s if (s := ceil((DB_RANGE/DB_STEP_SIZE)/NUM_LEDS)) <= NUM_LEDS else None #walrus operator, lol
+DB_COARSE_STEP_SIZE=DB_STEP_SIZE*DB_SETTINGS_PER_BIN
+
+DB_ACTIVE_BRIGHTNESS_BUMP=100 #indicate active status with brightness, not colour
+
+print("DB_SETTINGS_PER_BIN",DB_SETTINGS_PER_BIN)
+#helpful if the stepsize is a muliple of the settings_per_bin. Or just use //
+if DB_SETTINGS_PER_BIN>1:
+ if color_blind==True:
+ DB_INDICATOR_COLORS=clb.generalised_color_lut([(255,255,000),(000,255,000),],[0,255],256,256//(DB_SETTINGS_PER_BIN-1)) #-1 is for coarser steps to get full range mapped #shades of yellow/green
+ else:
+ DB_INDICATOR_COLORS=clb.generalised_color_lut([(255,255,000),(000,255,000),],[0,255],256,256//(DB_SETTINGS_PER_BIN-1)) #-1 is for coarser steps to get full range mapped #shades of yellow/green
+ print(DB_INDICATOR_COLORS)
+else:
+ if color_blind==True:
+ DB_INDICATOR_COLORS=[(255,000,000)] #active=red - stands out from blue.
+ pass
+ else:
+ DB_INDICATOR_COLORS=[(000,255,000)] #active=green
+
+
diff --git a/software/micropython/leds.py b/software/micropython/leds.py
index 7862253..6b11238 100644
--- a/software/micropython/leds.py
+++ b/software/micropython/leds.py
@@ -1,25 +1,20 @@
+import config
import asyncio
from random import randint
from neopixel import NeoPixel
from machine import Pin
from time import ticks_us, ticks_diff
-NUM_LEDS = 13 #50 #ugly work around for array out of bound error caused by ring buffer in mic.py
-DEV_STATUS_LED_PIN=21
-LEDS_PIN0 = 6
-LEDS_PIN1 = 8
-LEDS_PIN2 = 7
-
class Leds():
def __init__(self):
- gpioS = Pin(DEV_STATUS_LED_PIN, Pin.OUT)
- gpio0 = Pin(LEDS_PIN0, Pin.OUT)
- gpio1 = Pin(LEDS_PIN1, Pin.OUT)
- gpio2 = Pin(LEDS_PIN2, Pin.OUT)
+ gpioS = Pin(config.DEV_STATUS_LED_PIN, Pin.OUT)
+ gpio0 = Pin(config.LEDS_PIN0, Pin.OUT)
+ gpio1 = Pin(config.LEDS_PIN1, Pin.OUT)
+ gpio2 = Pin(config.LEDS_PIN2, Pin.OUT)
self.status_pix = NeoPixel(gpioS, 1, )
- self.neopix0 = NeoPixel(gpio0, NUM_LEDS)
- self.neopix1 = NeoPixel(gpio1, NUM_LEDS)
- self.neopix2 = NeoPixel(gpio2, NUM_LEDS)
+ self.neopix0 = NeoPixel(gpio0, config.NUM_LEDS)
+ self.neopix1 = NeoPixel(gpio1, config.NUM_LEDS)
+ self.neopix2 = NeoPixel(gpio2, config.NUM_LEDS)
self.led_list=[self.neopix0,self.neopix1,self.neopix2,self.status_pix]
def __iter__(self):
@@ -41,6 +36,10 @@ async def light(self, led_nr, values):
async def dance(self):
await self.blink()
+ async def show_rgb(self, led_arr_num, led_nr, rgb):
+ self.led_list[led_arr_num][led_nr] = rgb
+
+
async def show_hsv(self, led_arr_num, led_nr, hue, sat, val):
#show_hsv time to pixel: 3068 µs
t0 = ticks_us()
@@ -53,6 +52,9 @@ async def show_hsv(self, led_arr_num, led_nr, hue, sat, val):
async def write(self, led_arr_num):
self.led_list[led_arr_num].write()
+ async def fill(self, led_arr_num, colour):
+ self.led_list[led_arr_num].fill((colour))
+
#apparently not smooth
async def fade_rgb(self, led_arr_num, led_nr, target_hue, steps=30):
current_rgb = self.led_list[led_arr_num][led_nr]
diff --git a/software/micropython/main.py b/software/micropython/main.py
index 4eef8d6..9f9dbfd 100644
--- a/software/micropython/main.py
+++ b/software/micropython/main.py
@@ -4,20 +4,46 @@
from debug import set_global_exception
from touch import Touch
from menu import Menu
+import utime
+
+class Watchdog:
+ def __init__(self, check_interval_ms=100, alert_threshold_ms=200):
+ self.task_heartbeats={}
+ self.check_interval_ms=100
+ self.alert_threshold_ms=200
+
+ def heartbeat(self, taskname):
+ now=utime.ticks_ms()
+ self.task_heartbeats[taskname]=now
+
+
+ async def watch(self):
+ """Monitor all registered tasks for starvation"""
+ while True:
+ await asyncio.sleep_ms(self.check_interval_ms)
+ now = utime.ticks_ms()
+
+ for task_name, last_heartbeat in self.task_heartbeats.items():
+ elapsed = utime.ticks_diff(now, last_heartbeat)
+# print(f"{task_name} {elapsed}ms")
+ if elapsed > self.alert_threshold_ms:
+ print(f"⚠️ {task_name} hasn't run in {elapsed}ms!")
+
async def main():
#set_global_exception()
- touch0 = Touch(Pin(4))
- touch1 = Touch(Pin(3))
- touch2 = Touch(Pin(2))
- microphone = Mic()
- menu = Menu(microphone)
+ watchdog = Watchdog() #must be passed to each class object if being used.
+ touch0 = Touch(watchdog,Pin(4))
+ touch1 = Touch(watchdog,Pin(3))
+ touch2 = Touch(watchdog,Pin(2))
+ microphone = Mic(watchdog)
+ menu = Menu(watchdog,microphone)
menu.add_touch(touch0)
menu.add_touch(touch1)
menu.add_touch(touch2)
#print("Starting main gather...")
- await asyncio.gather(touch0.start(), touch1.start(), touch2.start(), menu.start(), microphone.start())
+ await asyncio.gather(watchdog.watch(),touch0.start(), touch1.start(), touch2.start(), menu.start(), microphone.start())
#await asyncio.gather(microphone.start())
try:
asyncio.run(main())
diff --git a/software/micropython/menu.py b/software/micropython/menu.py
index 1ad0340..1ded270 100644
--- a/software/micropython/menu.py
+++ b/software/micropython/menu.py
@@ -1,19 +1,36 @@
import asyncio
+import config
from time import ticks_ms, ticks_diff
+#there's a lot of janky double handling in this, but I'd have to rip it apart to fix up.
class Menu:
- def __init__(self, mic):
+ def __init__(self, watchdog, mic):
+ self.watchdog=watchdog
+
self.state_changed=False
self.menu_pix=[[],[],[],[],[],[],[],[],[],[],[],[]]#12 values to fill.
- self.main_modes=["Intensity","Synesthesia"]
+ self.main_modes=["intensity","synesthesia"]
self.main_mode_index=0
- self.sub_modes=[['brightness','LEDs_per_px','start_note','decibel_ceiling'],['brightness','LEDs_per_px','start_note','decibel_ceiling']] #On the wishlist but not critical path: 'hue_select'
+ self.main_mode=self.main_modes[self.main_mode_index]
+
+
+ self.sub_modes=['brightness','resolution','decibel_ceiling'] #On the wishlist but not critical path: 'hue_select'
self.sub_mode_index=0
- self.sub_sub_modes=[['max_db_set','min_db_set'],['max_db_set','min_db_set']]
- self.sub_sub_mode_index=0
+ self.sub_mode=self.sub_modes[self.sub_mode_index]
+
+ #dictionary entries should reflect their sub_mode name, so the sub_mode variable can be recycled/used to efficiently point to the right sub_sub modes
+ self.sub_sub_modes={'brightness':['flat','scaling'],'resolution':['notes_per_pix','panning'],'decibel_ceiling':['max_db_set','min_db_set']}
+ #the submodes need a dictionary of their indexes, so that one index is not shared across all submodes, leading to user inputs that appear to do nothing but are acutally wrapping the submode index around, making it appear like no mode switch has happened.
+ self.sub_sub_mode_indexes={'brightness':0,'resolution':0,'decibel_ceiling':0}
+ #actually store the sub_sub_mode for less verbose menue use
+ self.sub_sub_mode=self.sub_sub_modes[self.sub_mode][0]
+ self.sub_sub_mode_index=self.sub_sub_mode_indexes[self.sub_mode]
self.mic=mic
+ self.stored_brightness_index=self.mic.brightness_index
+ self.stored_low_db_set_point_setting=self.mic.low_db_set_point
+
self.touches = []
self.rvs = [0,0,0]
self.states = [False,False,False]
@@ -43,9 +60,32 @@ def add_touch(self, touch):
async def update_main_mode(self):
self.main_mode_index+=1
self.main_mode_index%=len(self.main_modes)
+
+ if self.main_modes[self.main_mode_index]=="Synesthesia":
+ #automatically rescale brightness to turn on faint blue LEDs so notes can be differentiated
+ self.stored_brightness_index=self.mic.brightness_index
+# self.mic.brightness_index=5 #lowest index that shows difference between A, A#, C, C#, ect...
+ self.mic.brightness=self.mic.brightnesses[self.mic.brightness_index]
+
+ #automatically rescale db range to analyse only the loudest notes - too visually noisy otherwise.
+ self.stored_low_db_set_point_setting=self.mic.low_db_set_point
+ self.mic.auto_low_control=True
+
+ else:
+ #return the brightness to the stored/user-selected value for intensity
+ self.mic.brightness_index=self.stored_brightness_index
+# #a hypothethical feature: make the bottom of the synesthesia mode cut out noise.
+# self.mic.auto_low_control=False
+ #return the decible range to the previous good-looking/user-set range.
+ self.mic.low_db_set_point=self.stored_low_db_set_point_setting
+
+
+ self.mic.menu_update_required=True
+
+
async def update_value(self, direction):
- if self.sub_modes[self.main_mode_index][self.sub_mode_index]=="brightness":
+ if self.sub_mode=="brightness":
# current_time=ticks_ms()
# button_held=ticks_diff(current_time,self.start_time,)
# print('button held (ms): ',button_held)
@@ -58,12 +98,18 @@ async def update_value(self, direction):
# print('Brightness step: ',brightness_step)
if direction=="+":
+ self.mic.show_menu_in_mic=True
+ self.mic.menu_update_required=True
+
if self.mic.brightness_index=1:
self.mic.brightness_index-=1
self.mic.brightness=self.mic.brightnesses[self.mic.brightness_index]
@@ -76,154 +122,212 @@ async def update_value(self, direction):
print("Brightness in menu: ", self.mic.brightness)
- elif self.sub_modes[self.main_mode_index][self.sub_mode_index]=="LEDs_per_px":
+ elif self.sub_mode=='resolution':
+ print("changing resolution")
- if direction=="+":
- self.mic.show_menu_in_mic=True
- self.mic.menu_update_required=True
-
-
- await self.mic.relocate_start_range_index()
- print("changed resolution, new start range index: ",self.mic.start_range_index)
+ #set the menu update required by the mic LED updater
+ self.mic.menu_thing_updating="resolution"
+
+ if self.mic.resolution_sub_mode=="notes_per_pix":
- if self.mic.notes_per_led_index=1:
+ self.mic.notes_per_led_index-=1
+ self.mic.notes_per_led=self.mic.notes_per_led_options[self.mic.notes_per_led_index]
+ print("Notes per LED: ", self.mic.notes_per_led)
- await self.mic.relocate_start_range_index()
- print("changed resolution (notes/led):", self.mic.notes_per_led, "new start range index: ",self.mic.start_range_index)
+ await self.mic.relocate_start_range_index()
+ print("changed resolution (notes/led):", self.mic.notes_per_led, "new start range index: ",self.mic.start_range_index)
-
- elif direction=="-":
- self.mic.show_menu_in_mic=True
- self.mic.menu_update_required=True
-
- if self.mic.notes_per_led_index>=1:
- self.mic.notes_per_led_index-=1
- self.mic.notes_per_led=self.mic.notes_per_led_options[self.mic.notes_per_led_index]
- print("Notes per LED: ", self.mic.notes_per_led)
-
- await self.mic.relocate_start_range_index()
- print("changed resolution (notes/led):", self.mic.notes_per_led, "new start range index: ",self.mic.start_range_index)
+
+ elif direction=="-":
+ self.mic.show_menu_in_mic=True
+ self.mic.menu_update_required=True
+
+ if self.mic.notes_per_led_index=self.mic.full_window_len-self.mic.number_of_octaves: #this check is important for when changing resolutions, adjusting so the user isn't left in a dead spot
- self.mic.start_range_index=self.mic.full_window_len-self.mic.number_of_octaves
+ elif direction=="u":
+ print("Notes per LED: ", self.mic.notes_per_led)
+ self.mic.show_menu_in_mic=True
+ self.mic.menu_update_required=True
+
+ return
+
+ elif self.mic.resolution_sub_mode=="panning":
+ if direction=="+":
+ self.mic.show_menu_in_mic=True
+ self.mic.menu_update_required=True
+
+ self.mic.start_range_index+=1
self.mic.absolute_note_index=self.mic.start_range_index*self.mic.notes_per_led
- self.mic.menu_update_required=True
-
- elif direction=="-":
- if self.mic.start_range_index>=1:
- self.mic.start_range_index-=1
- self.mic.absolute_note_index-=self.mic.notes_per_led
print("absolute_note: ",self.mic.absolute_note_index)
- if self.mic.start_range_index<=0:
- self.mic.star_range_index=0
- if self.mic.absolute_note_index<=0:
- self.mic.abolute_note_index=0
-# if self.mic.start_range_index<=-self.mic.max_window_overreach:
-# self.mic.start_range_index=-self.mic.max_window_overreach
- self.mic.menu_update_required=True
-
- elif direction=="u":
- #tell the menu that an update is required, needed or it will draw every frame.
- self.mic.menu_update_required=True
- #set the menu update required by the mic LED updater
- self.mic.menu_thing_updating="start_range_index"
-
- print("Start range index: ", self.mic.start_range_index)
- return
+ #these checks are probably better to do in the ol mic
+ if self.mic.start_range_index>=self.mic.full_window_len-self.mic.number_of_octaves: #this check is important for when changing resolutions, adjusting so the user isn't left in a dead spot
+ self.mic.start_range_index=self.mic.full_window_len-self.mic.number_of_octaves
+ self.mic.absolute_note_index=self.mic.start_range_index*self.mic.notes_per_led
+ self.mic.menu_update_required=True
+
+ elif direction=="-":
+ self.mic.show_menu_in_mic=True
+ self.mic.menu_update_required=True
+
+ if self.mic.start_range_index>=1:
+ self.mic.start_range_index-=1
+ self.mic.absolute_note_index-=self.mic.notes_per_led
+ print("absolute_note: ",self.mic.absolute_note_index)
+
+ if self.mic.start_range_index<=0:
+ self.mic.star_range_index=0
+ if self.mic.absolute_note_index<=0:
+ self.mic.abolute_note_index=0
+ # if self.mic.start_range_index<=-self.mic.max_window_overreach:
+ # self.mic.start_range_index=-self.mic.max_window_overreach
+ self.mic.menu_update_required=True
+
+ elif direction=="u":
+ #tell the menu that an update is required, needed or it will draw every frame.
+ self.mic.menu_update_required=True
+ self.mic.show_menu_in_mic=True
+
+
+ print("Start range index: ", self.mic.start_range_index)
+ return
#Need to make these one general call.
- elif self.sub_modes[self.main_mode_index][self.sub_mode_index]=="decibel_ceiling":
- print("tried to change decibel ceiling")
+ elif self.sub_mode=="decibel_ceiling":
+ print("changing decibel ceiling")
if direction=="+":
#tell the menu that an update is required, needed or it will draw every frame.
+ self.mic.show_menu_in_mic=True
self.mic.menu_update_required=True
#set the menu update required by the mic LED updater
self.mic.menu_thing_updating="highest_db"
if self.mic.db_selection=='max_db_set':
#control the movement of the pixel indicating the top of the colourmapping range
- if self.mic.max_db_set_point<=-20:
- self.mic.max_db_set_point+=10
+ if self.mic.max_db_set_point<=-config.DB_STEP_SIZE: #if the max_db is one step below 0db,
+ self.mic.max_db_set_point+=config.DB_STEP_SIZE #increment according to step_size
print("increased max db range to: ", self.mic.max_db_set_point)
- elif self.mic.max_db_set_point>-20:
+ elif self.mic.max_db_set_point>0:
print("can't increase maxDB, if this is a concern to your visualization quest, you need to get hearing protection")
else:
#control the position of the pixel indicating the bottom of the colourmapping range
- if self.mic.lowest_db<=self.mic.max_db_set_point-20:
- self.mic.lowest_db+=10
- print("increased min db range to: ", self.mic.lowest_db)
+ if self.mic.low_db_set_point<=self.mic.max_db_set_point-2*config.DB_COARSE_STEP_SIZE: #if the min_db is two bins below the max_db
+ self.mic.low_db_set_point+=config.DB_STEP_SIZE #increment to one bin below the max_db
+ print("increased min db range to: ", self.mic.low_db_set_point)
#make sure the min db value cant get too near to the max db value
- elif self.mic.lowest_db>self.mic.max_db_set_point-20:
- print("can't increase/raise to the lowest db, you'll lose all resolution")
+ elif self.mic.low_db_set_point>self.mic.max_db_set_point-2*config.DB_COARSE_STEP_SIZE:
+ print("can't increase/raise to the lowest db, you'll lose all resolution or overlap pixels")
if direction=="-":
#tell the menu that an update is required, needed or it will draw every frame.
+ self.mic.show_menu_in_mic=True
self.mic.menu_update_required=True
#set the menu update required by the mic LED updater
self.mic.menu_thing_updating="highest_db"
if self.mic.db_selection=='max_db_set':
#control the movement of the pixel indicating the top of the colourmapping range
- if self.mic.max_db_set_point>=self.mic.lowest_db+20:
- self.mic.max_db_set_point-=10
+ if self.mic.max_db_set_point>=self.mic.low_db_set_point+2*config.DB_COARSE_STEP_SIZE: #if the min_db is two bins below the mibn_db
+ self.mic.max_db_set_point-=config.DB_STEP_SIZE
print("decreased max db range to: ", self.mic.max_db_set_point)
- elif self.mic.max_db_set_point=-110:
- self.mic.lowest_db-=10
- print("decreased min db range to: ", self.mic.lowest_db)
+ if self.mic.low_db_set_point>=-120+config.DB_STEP_SIZE:
+ self.mic.low_db_set_point-=config.DB_STEP_SIZE
+ print("decreased min db range to: ", self.mic.low_db_set_point)
#make sure the min db value cant get too near to the max db value
- elif self.mic.lowest_db<-110:
+ elif self.mic.low_db_set_point<-120:
print("can't lower any further, you'll lose sight of the pixel. If you can hear down here, you must get overstimulated very easily.")
elif direction=="u":
#tell the menu that an update is required, needed or it will draw every frame.
+ self.mic.show_menu_in_mic=True
self.mic.menu_update_required=True
#set the menu update required by the mic LED updater
self.mic.menu_thing_updating="highest_db"
+ #perfrom updates last (here, last, once, instead of repeated inside each case)
+ self.mic.scale_and_clip_db_range[1]=self.mic.max_db_set_point
+ print("set max db in db-to-colour-index interp")
+ self.mic.scale_and_clip_db_range[0]=self.mic.low_db_set_point
+ print("set min db in db-to-colour-index interp")
+
return
+
async def change_submode(self,direction):
if direction=="+":
self.sub_mode_index+=1
- self.sub_mode_index%=len(self.sub_modes[self.main_mode_index])
+ self.sub_mode_index%=len(self.sub_modes)
elif direction=="-":
self.sub_mode_index-=1
- self.sub_mode_index%=len(self.sub_modes[self.main_mode_index])
+ self.sub_mode_index%=len(self.sub_modes)
- self.sub_mode=self.sub_modes[self.main_mode_index][self.sub_mode_index]
+ self.sub_mode=self.sub_modes[self.sub_mode_index]
print("Current sub-mode: ",self.sub_mode)
+ self.sub_sub_mode_index=self.sub_sub_mode_indexes[self.sub_mode]
+ print("Changed sub-mode's sub_sub_index to stored index:", self.sub_sub_mode_index)
+
+ return
+
+ async def update_sub_value(self):
+ if self.sub_mode=="brightness":
+ self.mic.brightness_sub_mode=self.sub_sub_mode
+ print("brightness submode changed to: ", self.mic.brightness_sub_mode)
+ self.mic.menu_update_required=True
+ self.mic.show_menu_in_mic=True
+
+ if self.sub_mode=="resolution":
+ self.mic.resolution_sub_mode=self.sub_sub_mode
+ print("resolution submode changed to: ", self.mic.resolution_sub_mode)
+ self.mic.menu_update_required=True
+ self.mic.show_menu_in_mic=True
+
+ #only change if in the decible select window.
+ if self.sub_mode=="decibel_ceiling":
+ self.mic.db_selection=self.sub_sub_mode
+ self.mic.menu_update_required=True
+ self.mic.show_menu_in_mic=True
+ return
+
async def change_sub_submode(self):
self.sub_sub_mode_index+=1
- self.sub_sub_mode_index%=len(self.sub_sub_modes)
- self.sub_sub_mode=self.sub_sub_modes[self.main_mode_index][self.sub_sub_mode_index]
- print("Current sub-sub-mode: ",self.sub_sub_mode)
+ self.sub_sub_mode_index%=len(self.sub_sub_modes[self.sub_mode])
+ #and update dictionary too:
+ self.sub_sub_mode_indexes[self.sub_mode]=self.sub_sub_mode_index
+
+ print("Changed sub-mode", self.sub_mode, "\'s sub_sub_index to stored index:", self.sub_sub_mode_index)
+
+ try:
+ #fetch the sub_sub_modes from a dictionary acording to the sub_modes and internal index thereof
+ self.sub_sub_mode=self.sub_sub_modes[self.sub_mode][self.sub_sub_mode_index]
+ print("Just changed sub-sub-mode to: ",self.sub_sub_mode)
+ except Exception as e:
+ print(e)
+ print("Probably that the dictionary key wasn't found")
+ #check button combinations: call setting functions, then call updating functions, which set values/"things_updating" flags in mic
async def update_menu(self):
#check each button combination and perform an action accordingly
if (self.states==[True,True,True] and self.state_changed!=True):
@@ -269,17 +373,16 @@ async def update_menu(self):
await self.update_value("u")
self.mic.show_menu_in_mic=True
- #Thought about adding more user control to the decibel levels, decided against, one level of menues too far. Just need a better AGC.
+ #Thought about adding more user control to the decibel levels, decided against, one level of menues too far. Just need a better AGC. So built one.
if (self.states==[False,False,True] and self.state_changed!=True and self.first_press==True):
self.first_press=False
self.menu_on_time=ticks_ms()
-
- #only change if in the decible select window.
- if self.sub_modes[self.main_mode_index][self.sub_mode_index]=="decibel_ceiling":
- await self.change_sub_submode()
- self.mic.db_selection=self.sub_sub_mode
- self.mic.show_menu_in_mic=True
-
+ print("Toggle sub_sub_mode")
+ #update sub_sub_mode
+ await self.change_sub_submode()
+ #set value flags/"how_things_update" in mic.
+ await self.update_sub_value()
+ self.mic.show_menu_in_mic=True
#toggle the menu on and off
if (self.states==[True,True,False] and self.state_changed!=True and self.first_press==True):
@@ -309,16 +412,21 @@ async def update_menu(self):
menu_time_out=10000
if menu_time_elapsed>menu_time_out:
self.mic.show_menu_in_mic=False
+ #comment out below if you want a permanent status LED
+ self.mic.status_led_off=True
except Exception as e:
print("exception:", e)
pass
async def start(self):
while True:
+# self.watchdog.heartbeat('Menu') #use if you want to be updating and reporting from the menu.
+
for index,touch in enumerate(self.touches):
self.states[index]=touch.state
self.rvs[index]=touch.rv
-# print("rv0", self.rvs[0], "rv1", self.rvs[1], "rv2", self.rvs[2])
+ if config.PRINT_TOUCH_READING==True:
+ print("rv0", self.rvs[0], "rv1", self.rvs[1], "rv2", self.rvs[2])
# print(self.states)
# print("rv0", self.rvs[0], "rv1", self.rvs[1], "rv2", self.rvs[2])
@@ -326,6 +434,7 @@ async def start(self):
#make the menu pause between updates
await asyncio.sleep_ms(400)
+# await asyncio.sleep_ms(0)
diff --git a/software/micropython/mic.py b/software/micropython/mic.py
index db810cb..8bec292 100644
--- a/software/micropython/mic.py
+++ b/software/micropython/mic.py
@@ -1,3 +1,6 @@
+#Welcome to the spaghetti monolith.
+
+import config
from ulab import utils
import json
import math
@@ -6,9 +9,14 @@
from ulab import numpy as np
from machine import Pin, I2S
from time import ticks_ms, ticks_diff
-from utils.border_calculator import PrecomputedValues
+from utils.border_calculator import PrecomputedBorders
+from utils.nearest_tone_index_calculator import PrecomputedNearestTones
+from utils.nearest_tone_index_represents import PrecomputedToneRepresentations
from utils.menu_calculator import PrecomputedMenu
+# import gc
+# gc.collect()
+
# 512 in the FFT 16000/512 ~ 30Hz update.
# DMA buffer should be at least twice, rounded to power of two.
SAMPLE_RATE = 8000 # Hz
@@ -38,40 +46,70 @@
# keep up (because need to perform FFT on full SAMPLE_COUNT for each iteration.)
samples = np.zeros(SAMPLE_COUNT, dtype=np.int16)
-sample_bytearray = samples.tobytes() # bytearray points to the sample samples array
+#claude sonnet says this is a big issue #sample_bytearray = samples.tobytes() # bytearray points to the sample samples array
+sample_bytearray=samples.tobytes()
scratchpad = np.zeros(2 * SAMPLE_COUNT) # re-usable RAM for the calculation of the FFT
# avoids memory fragmentation and thus OOM errors
-ID = 0
-SD = Pin(11)
-SCK = Pin(10)
-WS = Pin(9)
+magnitudes=np.zeros(SAMPLE_COUNT, dtype=np.float) #this is the result from the FFT
+
+V_ref=8388607 #this value is microphone dependant, for the DFROBOT mic, which is 24-bit I2S audio, that value is apparently 8,388,607
+
+ID = 0 #I2S identity
+SD = Pin(config.SD)
+SCK = Pin(config.SCK)
+WS = Pin(config.WS)
class Mic():
- def __init__(self):
+ def __init__(self,watchdog):
+ self.watchdog=watchdog
+
self.microphone = I2S(ID, sck=SCK, ws=WS, sd=SD, mode=I2S.RX,
bits=SAMPLE_SIZE, format=I2S.MONO, rate=SAMPLE_RATE,
ibuf=I2S_SAMPLE_BYTES)
- self.modes=["Intensity","Synesthesia"]
- self.mode=self.modes[0]
-# self.menu_pix=[[],[],[],[],[],[],[],[],[],[],[],[]]#12 values to fill.
+ self.mode="intensity"
+ self.status_led_off=False
self.show_menu_in_mic=False
self.menu_thing_updating="brightness"
self.menu_update_required=False
self.menu_init=True #hopefully just used once a start up.
-
- self.max_db_set_point=-40
+ #converts fft to db
+ self.db_scaling=np.zeros(config.NUM_LEDS,dtype=np.float)
+ self.max_db_set_point=config.BOOT_MAX_DB
self.highest_db_on_record=self.max_db_set_point
- self.lowest_db=-80
+ self.low_db_set_point=config.BOOT_MIN_DB
self.db_selection="max_db_set"
+ self.last_loudest_reading=-80
+ self.auto_low_control=False
+
+ #determines the values that are actually accounted for in display colour scaling
+ self.scale_and_clip_db_range=np.array([self.low_db_set_point, self.highest_db_on_record]) #for colouring: values chosen by looking at my spectrogram. I think a value of zero is a shockwave.
+ # Preallocated arrays
+ #stores result from fft
+ self.binned_fft_calc=np.zeros(config.NUM_LEDS,dtype=np.float)
+ self.dominant_tones=[0]*config.NUM_LEDS
+ self.dominant_notes_rep=np.zeros(config.NUM_LEDS,dtype=np.float)
+
+
+ self.fft_mags_array=np.zeros(config.NUM_LEDS,dtype=np.float)
+ self.fft_mags_int_list=[0]*config.NUM_LEDS
+
self.noise_floor=1000
- self.brightnesses=[2,3,4,5,7,10,20,35,50,90,160,255]
- self.brightness_index=4
+ self.resolution_sub_mode='notes_per_pix'
+
+ #intializing varables here is fine, but their handling and setting should be in the menu.
+ self.brightness_sub_mode='flat'
+ self.flat_hue=0
+ self.scaling_hue=10000
+
+
+ self.brightnesses=config.BRIGHTNESS_OPTIONS
+ self.brightness_index=config.BOOT_BRIGHTNESS_INDEX
self.brightness=self.brightnesses[self.brightness_index] #[0-255]
# Calculate the defined frequencies of the musical notes
@@ -81,7 +119,7 @@ def __init__(self):
# Event required to change note_per_led number
self.number_of_octaves=7
- self.notes_per_led_index=4
+ self.notes_per_led_index=config.BOOT_RESOLUTION_INDEX
self.notes_per_led_options=[1,2,3,4,6,12]
self.notes_per_led=self.notes_per_led_options[self.notes_per_led_index]
self.absolute_note_index=0
@@ -94,28 +132,54 @@ def __init__(self):
self.notes_per_pix_hue=0
self.octave_shift_hue=42000 #blue, determined by looking at hue learner.
+
+ #Auto gain control time flags
+ self.time_of_ceiling_raise=0
+ self.time_since_raise=0
+ self.spam_reduction_time=0
+ self.time_since_last_update=0
+
+
#load the precomupted octave menu and select the dictionary entry that corresponds to the current notes_per_led option
#create two buffers to avoid async clashes
self.precomputed_menus=PrecomputedMenu("utils/precomputed_octave_display.json")
if self.precomputed_menus.load():
JSON_menu=self.precomputed_menus.get(str(self.notes_per_led))
- self.menu_buffer_a=JSON_menu[self.start_range_index:12]
- self.menu_buffer_b=JSON_menu[self.start_range_index:12]
+ self.menu_buffer_a=JSON_menu[self.start_range_index:config.NUM_LEDS]
+ self.menu_buffer_b=JSON_menu[self.start_range_index:config.NUM_LEDS]
#load precomputed values and select the dictionary entry that corresponds to the current notes_per_led option
#create two buffers to avoid async clashes
- self.precomputed_borders=PrecomputedValues("utils/test_speedup_redo_values.json")
+ self.precomputed_borders=PrecomputedBorders("utils/trying for better divisions between A and Asharp.json")
if self.precomputed_borders.load():
JSON_boot=self.precomputed_borders.get(str(self.notes_per_led))
- self.fft_ranges_buffer_a=JSON_boot[self.start_range_index:12]
- self.fft_ranges_buffer_b=JSON_boot[self.start_range_index:12]
+ self.fft_ranges_buffer_a=JSON_boot[self.start_range_index:config.NUM_LEDS]
+ self.fft_ranges_buffer_b=JSON_boot[self.start_range_index:config.NUM_LEDS]
# print("FFT_ranges: ", self.fft_ranges_buffer_a)
+ #load precomputed values and select the dictionary entry that corresponds to the current notes_per_led option
+ #create two buffers to avoid async clashes
+ self.precomputed_representation_map=PrecomputedToneRepresentations("utils/binned_indexes_represent_which_musical_tones.json")
+ if self.precomputed_representation_map.load():
+ JSON_boot=self.precomputed_representation_map.get(str(self.notes_per_led))
+ self.representations_map_buffer_a=JSON_boot[self.start_range_index:config.NUM_LEDS]
+ self.representations_map_buffer_b=JSON_boot[self.start_range_index:config.NUM_LEDS]
+
+ #load precomputed values and select the dictionary entry that corresponds to the current notes_per_led option
+ #create two buffers to avoid async clashes
+ self.precomputed_closest_tone_indexes=PrecomputedNearestTones("utils/binned_indexes_of_tones_nearest_to_musical_notes.json")
+ if self.precomputed_closest_tone_indexes.load():
+ JSON_boot=self.precomputed_closest_tone_indexes.get(str(self.notes_per_led))
+ self.closest_tones_buffer_a=JSON_boot[self.start_range_index:config.NUM_LEDS]
+ self.closest_tones_buffer_b=JSON_boot[self.start_range_index:config.NUM_LEDS]
+
#create buffer pointers
self.active_buffer='a'
self.menu_to_operate_with=self.menu_buffer_a
self.fft_ranges_to_operate_with=self.fft_ranges_buffer_a
-
+ self.representations_map_to_operate_with=self.representations_map_buffer_a
+ self.closest_tone_indexes_to_operate_with=self.closest_tones_buffer_a
+
#create update flags
self.update_queued=False
self.next_data_key=None
@@ -124,36 +188,42 @@ def __init__(self):
self.ring_buffer_hues=np.zeros((3,self.length_of_leds-1))
self.ring_buffer_intensities=np.zeros((3,self.length_of_leds-1))
self.buff_index=0
- self.ring_buffer_hues_rgb=[((0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0)),
- ((0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0)),
- ((0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0))]
-
+ self.ring_buffer_hues_rgb=[[(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0)],
+ [(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0)],
+ [(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0),(0,0,0)]]
+
+ self.scaled_hues=[(0,0,0)]*12
+
+ self.ring_buffer_intensities_rgb=[[0,0,0,0,0,0,0,0,0,0,0,0],
+ [0,0,0,0,0,0,0,0,0,0,0,0],
+ [0,0,0,0,0,0,0,0,0,0,0,0]]
+
# Figure out what tones correspond to what magnitudes out of the fft, with respect to the mic sampling parameters
self.tones=FREQUENCY_RESOLUTION*np.arange(SAMPLE_COUNT/2)
-
- # Set the colours of notes in synaesthesia mode
-# hue_max=65535 #2^16, according to docs in leds.py
-# base_hue=0 #start with red #started learning with/listening to Kate Bush with: 40000
-# hue_diff=5400 #=65535/12=5400, to maximise the step sizes, with some tuning to get good deep blues, ect. #started learning with/listening to Kate Bush with: 5000
-# self.note_hues=np.arange(12.)
-# for i in np.arange(len(self.note_hues)):
-# self.note_hues[i]=(base_hue+(i*hue_diff))%65535
+
+ self.intensity_hues=[(0,0,0)]*12
+ #replace masks and HSV calcs with LUT
+# self.intensity_lut = create_color_lut()
+ self.intensity_lut = config.INTENSITY_COLOR_LUT
+
+ self.colour_index_range=np.array([0,255])
+# print("intensity_lut",self.intensity_lut)
#set hues for synesthesia mode based on notes picked in RGB in LED_note_hue_picker, translate to HSV values
- self.note_hues=[(255,0,0),(255,30,30),(255,60,0),(255,255,0),(255,255,30),(0,255,0),(80,220,10),(0,155,255),(0,0,255),(50,0,255),(255,0,255),(255,255,255)]
-
+ self.note_hues=config.SYN_NOTE_HUES
+
+
async def relocate_start_range_index(self):
#7 octaves, 12leds/xNotes, 12 Leds
self.start_range_index=math.floor(self.absolute_note_index/self.notes_per_led)
- print("relocated start range index",self.start_range_index)
+ #print("relocated start range index",self.start_range_index)
#self.absolute_note_index+=self.notes_per_led
#self.absolute_note_index-=self.notes_per_led
#The absolute note index Must always be a multiple of the notes_per_led, i.e. it must be rounded when the resolution is changed
#self.absolute_note_index=
-
def schedule_update(self,str_to_update):
#queue update
@@ -169,30 +239,40 @@ async def process_update(self):
#update inactive buffers, reading the precomputed dictionary using the requested notes_per_LED
inactive_menu_buffer=self.precomputed_menus.get(self.next_data_key)
inactive_fft_buffer_json=self.precomputed_borders.get(self.next_data_key)
+ inactive_representation_buffer=self.precomputed_representation_map.get(self.next_data_key)
+ inactive_closest_tone_buffer=self.precomputed_closest_tone_indexes.get(self.next_data_key)
+
self.full_window_len=len(inactive_fft_buffer_json)
# print("len full json array: ",len(inactive_fft_buffer_json))
- inactive_menu_range=inactive_menu_buffer[self.start_range_index:self.start_range_index+12]
- inactive_fft_buffer_ranges=inactive_fft_buffer_json[self.start_range_index:self.start_range_index+12]
+ inactive_menu_range=inactive_menu_buffer[self.start_range_index:self.start_range_index+config.NUM_LEDS]
+ inactive_fft_buffer_ranges=inactive_fft_buffer_json[self.start_range_index:self.start_range_index+config.NUM_LEDS]
+ inactive_representations=inactive_representation_buffer[self.start_range_index:self.start_range_index+config.NUM_LEDS]
+ insactive_closest_tones=inactive_closest_tone_buffer[self.start_range_index:self.start_range_index+config.NUM_LEDS]
self.window_slice_len=len(inactive_fft_buffer_ranges)
# print("window_slice_Len: ",self.window_slice_len)
- window_overextension=12-self.window_slice_len
+ window_overextension=config.NUM_LEDS-self.window_slice_len
- if len(inactive_fft_buffer_ranges)<12: #and window_overextension=0:
+ if f[0]>=0: #check if the bin has not been errored out with -1, e.g.: if the menu or bins are shorter than the display.
+
+ #total energy of sound is more important than an average. Not sure what this will do to my log conversion.
slice_sum = np.sum(magnitudes[f[0]:f[1]])
+ slice_sums.append(slice_sum)
slice_index_diff = f[1]-f[0]
try:
normalized_sum = slice_sum/slice_index_diff
- if normalized_sum < self.noise_floor:
- normalized_sum=0
- #set the dominant mag to be the first magnitude in the array slice, if they are all lower than the noise threshold.
- dominant_mag = magnitudes[f[0]] # Not ideal but doesn't matter, as the tone will be set to zero brightness
- dominant_tone = self.tones[f[0]]
- # Second block that could be if statemented into a display mode
- else:
- # Find out where the max magnitude in the slice is, then add the starting index of the slice,
- # or you'll get veeeery odd frequency curves.
- where_dominant_mag=np.argmax(magnitudes[f[0]:f[1]])+f[0]
- dominant_mag=magnitudes[where_dominant_mag]
- dominant_tone=self.tones[where_dominant_mag]
-
+ ###This sort of thing is needed for microtone representation.
+ ## Find out where the max magnitude in the slice is, then add the starting index of the slice,
+ ## or you'll get veeeery odd frequency curves.
+ where_dominant_mag=np.argmax(magnitudes[f[0]:f[1]])+f[0]
+ dominant_mag=magnitudes[where_dominant_mag]
+ dominant_tone=self.tones[where_dominant_mag]
+
# Crops up if the number of notes in a bin is too few.
# As in low note_per_bin cases.
- except ZeroDivisionError:
+ except Exception as e:
+ print("Exception: ",e)
#set the output to be the first value in the bin, always the first index, in this case
#creates an output with padded zeros.
- normalized_sum=0
- dominant_mag = magnitudes[f[0]]
- dominant_tone = self.tones[f[0]]
+ display_value=0
+ #set the dominant mag to be the first closes_tone to a 'real note's magnitude in the array slice, if they are all lower than the noise threshold.
+ dominant_mag = 0
+ #this should be the first closest /tone/ not the first halfway frequency
+ dominant_tone = 0
+# dominant_note_rep = 0
+
+
else:
+# print("bin errored out with -1")
normalized_sum=0 #can't set these to -1 because they go through a log filter
- dominant_mag=3000
+ dominant_mag=0
dominant_tone=0
+ dominant_note_rep=0
-# fftCalc.append(normalized_sum)
- fftCalc.append(dominant_mag)
- dominants.append(dominant_tone)
- num_led_bins_calculated=self.length_of_leds
-# #print("len of fftCalc in wled",len(fftCalc))
- if len(fftCalc)>num_led_bins_calculated:
- fftCalc=fftCalc[:num_led_bins_calculated:]
- dominants=dominants[:num_led_bins_calculated:]
-
- return fftCalc,dominants
+ self.binned_fft_calc[index]=dominant_mag
+ self.dominant_tones[index]=dominant_tone
+# self.dominant_notes_rep[index]=dominant_note_rep
+# print(slice_sums)
+# print("binned_fft_calc:",self.binned_fft_calc)
+# print("dominant_tones:",self.dominant_tones)
+# print("dominant_notes_rep:",self.dominant_notes_rep)
+
+ t_fft_bins1=ticks_ms()
+# print("fft_util:",ticks_diff(t_spectro_isolate_1,t_spectro_isolate_0), "binning_fft:",ticks_diff(t_fft_bins1,t_fft_bins0))
+# print(f"after binning: {gc.mem_free()}")
+
+ return
async def start(self):
leds = Leds()
@@ -288,12 +388,12 @@ def irq_handler(noop):
t_mic_sample = None
while True:
+ self.watchdog.heartbeat('Mic, FFT,and Colour')
t_awaiting = ticks_ms()
-# if t_mic_sample:
-# #print("sample processing : ", ticks_diff(t_awaiting, t_mic_sample), "ms")
await flag.wait()
-
+
+# print(f"Before I2S: {gc.mem_free()}")
t_mic_sample = ticks_ms()
# this number should be non-zero, so the other coros can run. but if it's large
# then can probably tune the buffer sizes to get more responsiveness
@@ -315,306 +415,425 @@ def irq_handler(noop):
n_slice += 1
if n_slice * I2S_SAMPLE_COUNT == SAMPLE_COUNT:
n_slice = 0
-
+ samples[:]=np.frombuffer(sample_bytearray,dtype=np.int16)
+# print(f"after I2S: {gc.mem_free()}")
-
+ t1=ticks_ms()
# Perform FFT over the entire 'samples' buffer, not just the small I2S_SAMPLE_COUNT chunk of it
# calculate fft_mag from samples
tfft1=ticks_ms()
- fft_mags,dominants = await self.mini_wled(samples)
- ##print("wled function:", ticks_diff(t2, t1), "ms") # 40ms
-
- # Assuming fft_mags is a numpy array
- fft_mags_array_raw = np.array(fft_mags)
- V_ref=8388607 #this value is microphone dependant, for the DFROBOT mic, which is 24-bit I2S audio, that value is apparently 8,388,607
- db_scaling=np.array([20*math.log10(fft_mags_array_raw[index]/V_ref) if value != 0 else self.lowest_db for index, value in enumerate(fft_mags_array_raw) ]) #the magic number -80 in this code is -80db, the lowest value on my phone spectrogram app, but it's typically recommended to be -inf
- ##print(db_scaling)
-
+ #fft_mags,dominants,reps =
+ await self.fft_and_bin(samples)
+ tfft2=ticks_ms()
+
+ mask = self.binned_fft_calc != 0 #set to 0 if some conditions are met in the fft_and_bin
+ self.db_scaling[mask] = 20 * np.log10(self.binned_fft_calc[mask] / V_ref)
+ self.db_scaling[~mask] = self.low_db_set_point
+# print(self.db_scaling)
+
+# tfft3=ticks_ms()
+# print("FFT_testing_scratchpad: ", ticks_diff(tfft2, tfft1))
+
# FFTscaling only the fft_mags_array, when quiet, the maximum ambient noise dynamically becomes bright, which is distracting.
# We need to make noise an ambient low level of intensity
- brightness_range=np.array([0,255])
+
+
+# print(f"after scaling to db range: {gc.mem_free()}")
+
#auto gain control
- loudest_reading=max(db_scaling)
- if loudest_reading>self.highest_db_on_record:
+ self.last_loudest_reading=max(self.db_scaling)
+
+ #if there is a peak, log it and start a gain lowering timer
+ if self.last_loudest_reading>self.highest_db_on_record:
# self.highest_db_on_record=0.8*self.highest_db_on_record+0.2*max(db_scaling)
- self.highest_db_on_record=loudest_reading
+ self.highest_db_on_record=self.last_loudest_reading
+
print("highest db recorded: ",self.highest_db_on_record)
# print("loud: raising db top. db: ", self.highest_db_on_record)
- time_of_ceiling_raise=ticks_ms()
- spam_reduction_time=ticks_ms()
- elif (loudest_readingself.max_db_set_point+1): #+1db is cheating the decay on the highest db value.
- time_since_raise=ticks_diff(ticks_ms(),time_of_ceiling_raise)
- if time_since_raise<3000:
- time_since_last_update=ticks_diff(ticks_ms(),spam_reduction_time)
- if time_since_last_update>500:#reduce the number of spam checks
- spam_reduction_time=ticks_ms()
+ self.time_of_ceiling_raise=ticks_ms()
+ self.spam_reduction_time=ticks_ms()
+
+ #if the last loudest sound is below the set max, then do checks on whether to lower the agc
+ elif (self.last_loudest_readingself.max_db_set_point+1): #+1db is cheating the decay on the highest db value.
+ try:
+ self.time_since_raise=ticks_diff(ticks_ms(),self.time_of_ceiling_raise)
+ except:
+ self.time_since_raise=3000
+ print("timing issue")
+
+ if self.time_since_raise<3000:
+ self.time_since_last_update=ticks_diff(ticks_ms(),self.spam_reduction_time)
+ if self.time_since_last_update>500:#reduce the number of spam checks
+ self.spam_reduction_time=ticks_ms()
# print("checking if enough time has passed to lower the AGC")
- elif ticks_diff(ticks_ms(),time_of_ceiling_raise)>3000: #hardcoded delay on the AGC
+
+ elif ticks_diff(ticks_ms(),self.time_of_ceiling_raise)>=3000: #hardcoded delay on the AGC
self.highest_db_on_record=0.9*self.highest_db_on_record+0.1*self.max_db_set_point
- time_since_last_update=ticks_diff(ticks_ms(),spam_reduction_time)
- if time_since_last_update>500:#reduce the number of spam checks
- spam_reduction_time=ticks_ms()
+
+ self.time_since_last_update=ticks_diff(ticks_ms(),self.spam_reduction_time)
+ if self.time_since_last_update>500:#reduce the number of spam checks
+ self.spam_reduction_time=ticks_ms()
# print("quiet: lowering db top to set point. db: ", self.highest_db_on_record)
-
+# print(f"after setting db range: {gc.mem_free()}")
- summed_magnitude_range=np.array([self.lowest_db, self.highest_db_on_record]) #values chosen by looking at my spectrogram. I think a value of zero is a shockwave.
+ #make sure to rescale the upper end of the db array that informs the colour map range in the below interp function
+ self.scale_and_clip_db_range[1]=max(self.max_db_set_point,self.highest_db_on_record)
#scale to 0-255 range, can/should scale up for more hue resolution
- fft_mags_array = np.interp(db_scaling, summed_magnitude_range, brightness_range)
-# print("FFT_mags_array: ", fft_mags_array)
- tfft2=ticks_ms()
-# print("FFT: ", ticks_diff(tfft2, tfft1)) #42-77
+ #
+ self.fft_mags_array = np.interp(self.db_scaling, self.scale_and_clip_db_range, self.colour_index_range)
+# print("FFT_mags_array: ", self.fft_mags_array)
+
+# print(f"after scaling fft to db range: {gc.mem_free()}")
+# print("FFT_mags_int_list: ", fft_mags_int_list)
+ tfft3=ticks_ms()
+# print("FFT: ", ticks_diff(tfft2, tfft1)) #42-77
# Apply cosmetics to values calculated above
tint1=ticks_ms()
- if self.mode=="Intensity":
- # Create masks for different hue ranges
- mask_blue_red = np.where(fft_mags_array <= 170,1,0)
- mask_red_yellow = np.where(fft_mags_array > 170,1,0)
-
- # Define ranges and target mappings
- original_range_blue_red = np.array([0, 170])
- target_range_blue_red = np.array([32768, 65535])
-
- original_range_red_yellow = np.array([171, 255])
- target_range_red_yellow = np.array([0, 16320])
-
- # Interpolate for hues
- hue_blue_red = np.where(mask_blue_red, np.interp(fft_mags_array, original_range_blue_red, target_range_blue_red),0)
- hue_red_yellow = np.where(mask_red_yellow, np.interp(fft_mags_array, original_range_red_yellow, target_range_red_yellow),0)
-
- # Combine hue results
- intensity_hues = np.where(mask_blue_red, hue_blue_red, hue_red_yellow)
-
- # scale brightness of magnitudes following their hue calculation
- fft_mags_array*=(self.brightness/255)
-
- # Use async to call show_hsv for valid LEDs
- for i in range(0,len(fft_mags_array)):
-# self.channel1hues[i][self.bufferIndex+1%3]=hue
- await leds.show_hsv(0, i, int(intensity_hues[i]), 255, int(fft_mags_array[i]))#the FFT magnitude array brightness is already set by the range mapping functions above
+ if self.mode=="intensity":
+ for i in range(len(self.fft_mags_array)):
+ if self.db_scaling[i]>self.low_db_set_point:
+ if self.brightness_sub_mode=='flat':
+ self.scaled_hues[i]=(
+ (self.intensity_lut[round(self.fft_mags_array[i])][0]*self.brightness)//255,
+ (self.intensity_lut[round(self.fft_mags_array[i])][1]*self.brightness)//255,
+ (self.intensity_lut[round(self.fft_mags_array[i])][2]*self.brightness)//255
+ )
- #sorry about the -1s in the first terms, those are due to the length_of_leds being reduced by other array calculations/border conditions
- #the negative modulo terms in the second and fourth terms, however, are needed to get the ring buffer to work.
- await leds.show_hsv(1, i, int(self.ring_buffer_hues[(self.buff_index-2)%-3][i]), 255, int((self.ring_buffer_intensities[(self.buff_index-2)%-3][i])))
+ if self.brightness_sub_mode=="scaling":
+ self.scaled_hues[i]=(
+ int(self.intensity_lut[round(self.fft_mags_array[i])][0]*self.brightness*(self.fft_mags_array[i]/255))//255,
+ int(self.intensity_lut[round(self.fft_mags_array[i])][1]*self.brightness*(self.fft_mags_array[i]/255))//255,
+ int(self.intensity_lut[round(self.fft_mags_array[i])][2]*self.brightness*(self.fft_mags_array[i]/255))//255
+ )
+
+ else:
+ self.scaled_hues[i]=(0,0,0)
+
+ for i in range(len(self.fft_mags_array)):
+# self.intensity_hues[i]=self.intensity_lut[round(self.fft_mags_array[i])]
+ await leds.show_rgb(0,i,self.scaled_hues[i])
+ await leds.show_rgb(1,i,self.ring_buffer_hues_rgb[(self.buff_index)][i])
if self.show_menu_in_mic == False:
- await leds.show_hsv(2, i, int(self.ring_buffer_hues[(self.buff_index-1)%-3][i]), 255, int((self.ring_buffer_intensities[(self.buff_index-1)%-3][i])))
-
-
- self.ring_buffer_hues[(self.buff_index-1)%-3]=intensity_hues
- self.ring_buffer_intensities[(self.buff_index-1)%-3]=fft_mags_array
- self.buff_index-=1
- self.buff_index%=-3
+ await leds.show_rgb(2,i,self.ring_buffer_hues_rgb[(self.buff_index-1)%-3][i])
+#
+ tint2=ticks_ms()
+# print(self.ring_buffer_hues_rgb)
+# print(self.ring_buffer_intensities_rgb)
+ self.buff_index = (self.buff_index + 1) % 3
await leds.write(0)
await leds.write(1)
await leds.write(2)
- tint2=ticks_ms()
-# print("Intensity: ", ticks_diff(tint2, tint1)) #9-10
-
-
- tsyn1 = ticks_ms()
- if self.mode=="Synesthesia":
- # scale brightness of magnitudes following their hue calculation
- #this line is repeated above inside the intesities mode, but there it has to be after some hue calculatations
- fft_mags_array*=(self.brightness/255)
- dominants_array=np.array(dominants)
- dominants_notes=np.arange(len(dominants_array))
- # This line stumped me for an hour: it initializes as uint16, which causes the note calculation to overflow.
- # Causing negative numbers, causing green spikes of full brightness (FIXME)
- current_hues=[(),(),(),(),(),(),(),(),(),(),(),()]
+ # Second pass: update ring buffer AFTER displaying
+ for i in range(len(self.fft_mags_array)):
+ self.ring_buffer_hues_rgb[self.buff_index][i] = self.scaled_hues[i]
+# self.ring_buffer_intensities_rgb[self.buff_index][i] = round(self.fft_mags_array[i])
- #for each frequency, calculate what note it is, and map that to one of 12 selected hues.
- for i in range(len(dominants_array)):
- # See wikipedia: https://en.wikipedia.org/wiki/Piano_key_frequencies
- #If the log is infinite, just set the note to zero. This changes which note hue is indexed
- try:
- note=int(12.*np.log2(dominants_array[i]/440.)+49.)
- dominants_notes[i]=note%12
- current_hues[i]=self.note_hues[note%12]
- except:
- note=(0,0,0)
- current_hues[i]=note
-# if note<0:
-# note=0
-# print("current_hues:" , current_hues)
-# print("fft_mags_array:" , fft_mags_array)
+# print(f"after writing LEDs and ring buffers: {gc.mem_free()}")
- fractional_brightness=fft_mags_array/255
-# print("fract_bright", fractional_brightness)
-
- scaled_hues=[tuple(int(sub_hue*fractional_brightness[index]) for sub_hue in hue) for index,hue in enumerate(current_hues)]
-
-# print(scaled_hues)
+ tint3=ticks_ms()
+# print("Intensity: ", ticks_diff(tint2, tint1)) #9-10
+
+
+ tsyn1 = ticks_ms()
+ if self.mode=="synesthesia":
+ for i in range(len(self.dominant_tones)):
+
+ if self.db_scaling[i]0: #the menu pan sets 'outside of range' pixels to -1
+ self.dominant_notes_rep[i]=12.*np.log2(self.dominant_tones[i]/440.)+49.
+ note=round(self.dominant_notes_rep[i]-1)%12 #the -1 is to go from notes starting at 1 for A0 to starting at 0 for the hue index
+ else:
+ note=0
+
+ #this works to present 'flat' notes: no scaling of brightness with the intensity of the note
+ if self.brightness_sub_mode=='flat':
+ self.scaled_hues[i]=(
+ (self.note_hues[note][0]*self.brightness)//255,
+ (self.note_hues[note][1]*self.brightness)//255,
+ (self.note_hues[note][2]*self.brightness)//255)
+
+ #uncomment this if you want 'bright' notes: notes that scale with their played intensity. Cap to the lowest brightness that differentiates hues.
+ if self.brightness_sub_mode=='scaling':
+ self.scaled_hues[i]=(
+ int(self.note_hues[note][0]*(self.brightness*(self.fft_mags_array[i]/255)))//255,
+ int(self.note_hues[note][1]*(self.brightness*(self.fft_mags_array[i]/255)))//255,
+ int(self.note_hues[note][2]*(self.brightness*(self.fft_mags_array[i]/255)))//255)
+# print(self.scaled_hues[i])
+
+ #too fancy for own good: microtone representation- a colour interperlation for where the dominant frequency in a bin is on the colour scale
+# note_frac=note%1
+# lower_index=int(note)%12 #12 is the length of the hues I have chosen
+# upper_index=(lower_index+1)%12
+# lower_r,lower_g,lower_b=self.note_hues[lower_index]
+# upper_r,upper_g,upper_b=self.note_hues[upper_index]
+#
+# self.scaled_hues[i]=(
+# (int(lower_r+note_frac*(upper_r-lower_r))*self.brightness)//255,
+# (int(lower_g+note_frac*(upper_g-lower_g))*self.brightness)//255,
+# (int(lower_b+note_frac*(upper_b-lower_b))*self.brightness)//255,
+# )
+
- for i in range(0,len(fft_mags_array)):
- await leds.show_rgb(0, i, scaled_hues[i])
- await leds.show_rgb(1, i, self.ring_buffer_hues_rgb[(self.buff_index-2)%-3][i])
+ for i in range(len(self.dominant_notes_rep)):
+ await leds.show_rgb(0,i,self.scaled_hues[i])
+ await leds.show_rgb(1,i,self.ring_buffer_hues_rgb[(self.buff_index)][i])
if self.show_menu_in_mic == False:
- await leds.show_rgb(2, i, self.ring_buffer_hues_rgb[(self.buff_index-1)%-3][i])
-
-#
- self.ring_buffer_hues_rgb[(self.buff_index-1)%-3]=scaled_hues
- self.buff_index-=1
- self.buff_index%=-3
+ await leds.show_rgb(2,i,self.ring_buffer_hues_rgb[(self.buff_index-1)%-3][i])
+ self.buff_index = (self.buff_index + 1) % 3
await leds.write(0)
await leds.write(1)
await leds.write(2)
+
+ # Second pass: update ring buffer AFTER displaying
+ for i in range(len(self.dominant_notes_rep)):
+ self.ring_buffer_hues_rgb[self.buff_index][i] = self.scaled_hues[i]
+# self.ring_buffer_intensities_rgb[self.buff_index][i] = round(self.fft_mags_array[i])
+
+# print(self.dominant_notes_rep)
tsyn2=ticks_ms()
# print("synesthesia: ", ticks_diff(tsyn2, tsyn1)) #11-13
+# print(f"after colouring: {gc.mem_free()}")
tmenu1=ticks_ms()
if self.menu_init==True: #annoying to have a single use line but this is a quick fix.
- #init the status pix or it will keep the last menu state
+ #init the status pix or it will keep the last power-off menu state
leds.status_pix[0]=(0,20,0)#the status LED is grb
await leds.write(3)
self.menu_init=False
if self.show_menu_in_mic == True:
+ #self.mode_renderer.render() #this is possible, but the current monolith is not broken, and the prospect of passing back flags was giving me a headache, so I ditched it.
+ #The actual target is making the menu scale and offset according to a config.
+
if self.menu_thing_updating=="brightness" and self.menu_update_required==True:
- #update onboard LED/mini-menu
-# leds.show_hsv(3,0,0,0,10)
- leds.status_pix[0]=(0,20,0)#the status LED is grb
- await leds.write(3)
+ self.status_led_off=False
#print("brightness in mic: ",self.brightness)
+ #clear menu
+ await leds.fill(2,(0,0,0))
#print make the first pixel, left to right, show with brightness of the display, in one channel only (e.g. red)
- await leds.show_hsv(2,11,0,255,int(self.brightness))
-
- parts_per_bin=21 #255/12
+ if self.brightness_sub_mode=='flat':
+ #update onboard LED/mini-menu
+ leds.status_pix[0]=(0,20,0)#the status LED is grb
+ await leds.write(3)
+ await leds.show_hsv(2,config.MENU_SIZE+config.MENU_LED_OFFSET,self.flat_hue,255,int(self.brightness))
+ else:
+ #update onboard LED/mini-menu
+ leds.status_pix[0]=(15,20,0)#the status LED is grb
+ await leds.write(3)
+ await leds.show_hsv(2,config.MENU_SIZE+config.MENU_LED_OFFSET,self.scaling_hue,255,int(self.brightness))
+
#skip the first pixel, it's already been set.
- for i in range(1,12):
+ for i in range(1,config.MENU_SIZE+1): #+1 because range drops last pixel
#if the pixel is at the brightness index
if i==self.brightness_index:
- await leds.show_hsv(2,11-i,0,255,int(self.brightness))
+ if self.brightness_sub_mode=='flat':
+ await leds.show_hsv(2,config.MENU_SIZE+config.MENU_LED_OFFSET-i,self.flat_hue,255,int(self.brightness)) #-i arrangement is to make the menu work left to right
+ else:
+ await leds.show_hsv(2,config.MENU_SIZE+config.MENU_LED_OFFSET-i,self.scaling_hue,255,int(self.brightness))
+
# otherwise, blank out the non needed menu pixels
+ elif i==config.MENU_SIZE:
+ pass
else:
- await leds.show_hsv(2,11-i,0,0,0)
+ await leds.show_hsv(2,config.MENU_SIZE+config.MENU_LED_OFFSET-i,0,0,0)
#reset to allow the next update
self.menu_update_required=False
- if self.menu_thing_updating=="notes_per_px" and self.menu_update_required==True:
- #update onboard LED/mini-menu
- leds.status_pix[0]=(10,20,0)#the status LED is grb
- await leds.write(3)
-
- #update fft_ranges if needed
- self.schedule_update(str(self.notes_per_led))
- if self.update_queued:
- await self.process_update()
-
- for i in range(0,12): #blank out LEDs
- await leds.show_hsv(2,i,0,0,0)
- #3print(self.menu_to_operate_with)
- if self.menu_to_operate_with[i]==-1:
+ if self.menu_thing_updating=="resolution" and self.menu_update_required==True:
+ if self.resolution_sub_mode=="notes_per_pix" and self.menu_update_required==True:
+ self.status_led_off=False
+
+ #update onboard LED/mini-menu
+ leds.status_pix[0]=(5,30,0)#the status LED is grb
+ await leds.write(3)
+
+ #update fft_ranges if needed
+ self.schedule_update(str(self.notes_per_led))
+ if self.update_queued:
+ await self.process_update()
+
+ for i in range(0,config.NUM_LEDS): #blank out LEDs
await leds.show_hsv(2,i,0,0,0)
-# await leds.show_hsv(2,i,self.notes_per_pix_hue,255,int(self.brightness*0.1))
- elif self.menu_to_operate_with[i]>=0:
- await leds.show_hsv(2,i,self.menu_to_operate_with[i],255,self.brightness)
-
-# for i in range(0,self.window_slice_len,int(12/self.notes_per_led)): #the division of 12 is required to scale the right way around, six notes per led should show an octave every two leds, not every six
-# await leds.show_hsv(2,i,900*i,255,self.brightness) #make each octave a different colour
- self.menu_update_required=False
-
- if self.menu_thing_updating=="start_range_index" and self.menu_update_required==True:
- #update onboard LED/mini-menu
- leds.status_pix[0]=(0,0,20)#the status LED is grb, blue is distinct, the purple turns to red through the flex.
- await leds.write(3)
-
-# if self.start_range_index>=self.window_slice_len+self.max_window_overreach:
-# self.start_range_index=self.window_slice_len+self.max_window_overreach
-
- #update fft_ranges if needed
- self.schedule_update(str(self.notes_per_led))
- if self.update_queued:
- await self.process_update()
-
- #3print("start_range_index_in_mic: ",self.start_range_index)
- for i in range(0,12): #blank out LEDs
- await leds.show_hsv(2,i,0,0,0)
- if self.menu_to_operate_with[i]>=0:
- await leds.show_hsv(2,i,self.menu_to_operate_with[i]+self.octave_shift_hue,255,self.brightness)
-
+ #3print(self.menu_to_operate_with)
+ try:
+ if self.menu_to_operate_with[i]==-1:
+ await leds.show_hsv(2,i,0,0,0)
+ # await leds.show_hsv(2,i,self.notes_per_pix_hue,255,int(self.brightness*0.1))
+ elif self.menu_to_operate_with[i]>=0:
+ await leds.show_hsv(2,i,self.menu_to_operate_with[i],255,self.brightness)
+ except:
+ await leds.show_hsv(2,i,0,0,0)
+
+ # for i in range(0,self.window_slice_len,int(12/self.notes_per_led)): #the division of 12 is required to scale the right way around, six notes per led should show an octave every two leds, not every six
+ # await leds.show_hsv(2,i,900*i,255,self.brightness) #make each octave a different colour
+ self.menu_update_required=False
+
+ if self.resolution_sub_mode=="panning" and self.menu_update_required==True:
+ self.status_led_off=False
+
+ #update onboard LED/mini-menu
+ leds.status_pix[0]=(0,0,20)#the status LED is grb, blue is distinct, the purple turns to red through the flex.
+ await leds.write(3)
+
+ # if self.start_range_index>=self.window_slice_len+self.max_window_overreach:
+ # self.start_range_index=self.window_slice_len+self.max_window_overreach
+
+ #update fft_ranges if needed
+ self.schedule_update(str(self.notes_per_led))
+ if self.update_queued:
+ await self.process_update()
+
+ #3print("start_range_index_in_mic: ",self.start_range_index)
+ for i in range(0,config.NUM_LEDS): #blank out LEDs
+ await leds.show_hsv(2,i,0,0,0)
+ if self.menu_to_operate_with[i]>=0:
+ await leds.show_hsv(2,i,self.menu_to_operate_with[i]+self.octave_shift_hue,255,self.brightness)
+
- self.menu_update_required=False
+ self.menu_update_required=False
if self.menu_thing_updating=="highest_db" and self.menu_update_required==True:
+ self.status_led_off=False
+
#update onboard LED/mini-menu
leds.status_pix[0]=(20,0,0)#the status LED is grb
await leds.write(3)
#print("loudest reading: ", loudest_reading)
- db_per_bin=-10 #-120 to 0 decibels makes a nice 10 decible scale bar
- #for loop looks odd, because again it's decibels, and because I flipped it to be left to right
- for i in range(12,0,-1):
+ #-120 to 0 decibels makes a nice 10 decible scale bar
+ #db_settings_per_bin=
+
+ #for loop looks odd, because again it's decibels, and because I flipped it to be left to right. -1 to ensure 0 index is included
+ for i in range(config.MENU_SIZE,-1,-1):
#conditions will look odd here because the values to work with are in decibels, which are -ve
- if i*db_per_bin <= loudest_reading:
+ #-1 required to move positive step size into -120 to 0 range.
+
+ #blue, shows traditional 'eq' meter like effect
+ if -1*i*config.DB_COARSE_STEP_SIZE <= self.last_loudest_reading:
#draw loudest measured decibel signal, from -120 to 0
- await leds.show_hsv(2,i-1,self.octave_shift_hue,255,int(self.brightness*0.5))#annoying indicies, minus one is to line up with pixels
+ await leds.show_hsv(2,i,self.octave_shift_hue,255,int(self.brightness*0.5))
else:
#blank out leds
- await leds.show_hsv(2,i-1,0,0,0)
+ await leds.show_hsv(2,i,0,0,0)
- #draw level top first, so that it does not overide the highest db pixel indicator, in the case the highest value is greater than the high db but less than the next pixel
+ #draw the peak first in orange ish - so that it does not overide the highest db setting pixel indicator, in the case the highest value is greater than the high db but less than the next pixel
if (self.highest_db_on_record>self.max_db_set_point):
# if (i*db_per_bin <= loudest_reading < (i-1)*db_per_bin):
# await leds.show_hsv(2,i-1,5000,255,int(self.brightness))
- if (i*db_per_bin <= self.highest_db_on_record < (i-1)*db_per_bin):
- await leds.show_hsv(2,i-1,5000,255,int(self.brightness*0.5))
+ #
+ if (-1*i*config.DB_COARSE_STEP_SIZE <= self.highest_db_on_record < -1*(i-1)*config.DB_COARSE_STEP_SIZE):
+ await leds.show_hsv(2,i,5000,255,int(self.brightness*0.5))
+
+ #more complex for a crunched display
+ #derivation explained:
+ # rem = set_db_value/(resolution_or_step_size*db_settings_per_bin)
+ rem_low=(self.low_db_set_point%(config.DB_SETTINGS_PER_BIN*config.DB_STEP_SIZE))
+# print('rem0',rem0)
+ rem_high=(self.max_db_set_point%(config.DB_SETTINGS_PER_BIN*config.DB_STEP_SIZE))
+# print('rem1',rem1)
+ # step_size/rem: compute integer multiple that the remainder makes with respect to the size of a bin.
+ if rem_low!=0:
+ sub_val_low=rem_low//config.DB_STEP_SIZE
+ else:
+ sub_val_low=0
+# print('sub_val0',sub_val0)
+
+ if rem_high!=0:
+ sub_val_high=rem_high//config.DB_STEP_SIZE
+ else:
+ sub_val_high=0
+# print('sub_val1',sub_val1)
+
+ if self.db_selection=='min_db_set':
+ active_color=config.DB_INDICATOR_COLORS[sub_val_low]
+ inactive_color=config.DB_INDICATOR_COLORS[sub_val_high]
+ else:
+ active_color=config.DB_INDICATOR_COLORS[sub_val_high]
+ inactive_color=config.DB_INDICATOR_COLORS[sub_val_low]
+
+ #Set/scale indicator acording to brightness
+ #and handle which is the marked the active/inactive LEDS
+ active_rgb=(
+ ((active_color[0]*self.brightness)+config.DB_ACTIVE_BRIGHTNESS_BUMP)//255,
+ ((active_color[1]*self.brightness)+config.DB_ACTIVE_BRIGHTNESS_BUMP)//255,
+ ((active_color[2]*self.brightness)+config.DB_ACTIVE_BRIGHTNESS_BUMP)//255,)
+ inactive_rgb=(
+ ((inactive_color[0]*self.brightness)//3)//255,
+ ((inactive_color[1]*self.brightness)//3)//255,
+ ((inactive_color[2]*self.brightness)//3)//255)
+
+
#draw lowest db setting
- if i*db_per_bin==self.lowest_db:
+ if (-1*i*config.DB_COARSE_STEP_SIZE <= self.low_db_set_point < -1*(i-1)*config.DB_COARSE_STEP_SIZE):
if self.db_selection=='min_db_set':
- await leds.show_hsv(2,i-1,20000,255,int(self.brightness*0.5))#green is bright as
+ await leds.show_rgb(2,i,active_rgb)
else:
- await leds.show_hsv(2,i-1,0,255,int(self.brightness*0.5))
+ await leds.show_rgb(2,i,inactive_rgb)
+
#draw highest db setting
- if i*db_per_bin==self.max_db_set_point:
+ if (-1*i*config.DB_COARSE_STEP_SIZE <= self.max_db_set_point < -1*(i-1)*config.DB_COARSE_STEP_SIZE):
+ #check if there are sub_values in the db setting (sensitive setting)
if self.db_selection=='max_db_set':
- await leds.show_hsv(2,i-1,20000,255,int(self.brightness*0.5))#green is bright as
+ await leds.show_rgb(2,i,active_rgb)
else:
- await leds.show_hsv(2,i-1,0,255,int(self.brightness*0.5))
- #draw update the menu?
+ await leds.show_rgb(2,i,inactive_rgb)
+
+ if self.status_led_off==True:
+ #update onboard LED/mini-menu
+ leds.status_pix[0]=(0,0,0)#the status LED is grb
+ await leds.write(3)
+
+ #draw update the menu?
# await leds.write(2)
-
+
#This determines if the menue keep updating or is a one and done?
# self.menu_update_required=False
- if self.menu_thing_updating=="hue_select" and self.menu_update_required==True:
- #update onboard LED/mini-menu
- leds.status_pix[0]=(0,0,20)#the status LED is grb
- await leds.write(3)
-
- for i in range(0,12):
- await leds.show_hsv(2,i,0,0,0)
+# if self.menu_thing_updating=="hue_select" and self.menu_update_required==True:
+# #update onboard LED/mini-menu
+# leds.status_pix[0]=(0,0,20)#the status LED is grb
+# await leds.write(3)
+#
+# for i in range(0,12):
+# await leds.show_hsv(2,i,0,0,0)
tmenu2=ticks_ms()
-# print("menuing: ", ticks_diff(tmenu2, tmenu1)) #0
-
-
-
-# print("time spent awaiting: ", ticks_diff(t_mic_sample, t_awaiting), "FFT: ", ticks_diff(tfft2, tfft1),"Intensity: ", ticks_diff(tint2, tint1), "synesthesia: ", ticks_diff(tsyn2, tsyn1),"menuing: ", ticks_diff(tmenu2, tmenu1), "total", ticks_diff(tmenu2,t_awaiting))
total_ms=ticks_diff(tmenu2,t_awaiting)
-
+# print(f"after menuing: {gc.mem_free()}")
#Smooth to a consistent fps, which looks nicer, imo.
fps=15
frame_time=1000//fps
if total_ms < frame_time:
wait_time=frame_time-total_ms
+# leds.status_pix[0]=(0,0,0)#the status LED is grb
+# await leds.write(3)
else:
wait_time=0
+# leds.status_pix[0]=(50,50,50)#the status LED is grb
+# await leds.write(3)
await asyncio.sleep_ms(wait_time) #yeild control. #this one line appears to have made the program substantially more responsive in the menu side of things.
-# print("total (ms)", total_ms, "fps:", 1000/total_ms, "delay:", wait_time)
-
-
+
+# if self.mode=="Synesthesia":
+# print("total (ms)", total_ms, "mic_sample",ticks_diff(t1,t0), "fft_and_bin: ", ticks_diff(tfft2, tfft1), "synesthesia: ", ticks_diff(tsyn2, tsyn1), "fps:", 1000//total_ms, "delay:", wait_time)
+# else:
+# print("total (ms)", total_ms, "mic_sample",ticks_diff(t1,t0), "fft_and_bin: ", ticks_diff(tfft2, tfft1), "Intensity update 0 and buff: ", ticks_diff(tint2, tint1),"Intensity write LEDs: ", ticks_diff(tint3, tint2), "fps:", 1000//total_ms, "delay:", wait_time)
+#
diff --git a/software/micropython/touch.py b/software/micropython/touch.py
index 0a0bbce..a99df0c 100644
--- a/software/micropython/touch.py
+++ b/software/micropython/touch.py
@@ -1,6 +1,7 @@
# Inspired by: https://github.com/peterhinch/micropython-async/blob/master/v3/primitives/pushbutton.py
import time
import asyncio
+import config
try:
from machine import TouchPad
@@ -12,19 +13,23 @@ class Touch:
thresh = (80 << 8) // 100 # 80%
debounce_ms = 50
- def __init__(self, pin):
+ def __init__(self, watchdog, pin):
+ self.watchdog=watchdog
+
self.state = False
self.rv = 0
self._maxrawval = 0
- self.no_touch=70000
- self.no_touch_noise=40000
+ self.pin=pin
+
+ self.no_touch=config.UNPRESSED_CAPACITIVE_READING
+# self.no_touch_noise=40000
self.hard_coded_no_touch=self.no_touch
- self.no_touch_noises=[]
- self.one_touch=80000
+# self.no_touch_noises=[]
+ self.one_touch=config.PRESSED_CAPACITIVE_READING
self.hard_coded_touch=self.one_touch
- self.one_touch_noise=40000
- self.no_touch_noises=[]
+# self.one_touch_noise=40000
+# self.no_touch_noises=[]
try:
print("Initialising touch sensor")
@@ -38,6 +43,8 @@ async def start(self):
self.state = await self.rawstate() # Initial state
while True:
+ self.watchdog.heartbeat(f'Touch{self.pin}')
+
await self.rawstate()
# await self._check(await self.rawstate())
# Ignore state changes until switch has settled. Also avoid hogging CPU.
diff --git a/software/micropython/utils/LED_note_hue_picker.py b/software/micropython/utils/LED_note_hue_picker.py
index e69dff6..3af8cf4 100644
--- a/software/micropython/utils/LED_note_hue_picker.py
+++ b/software/micropython/utils/LED_note_hue_picker.py
@@ -17,9 +17,16 @@
#testing Asharp peach[(255,0,0),(255,30,30),(255,60,0),(255,255,0),(80,255,0),(0,255,0),(0,255,30),(0,155,255),(0,0,255),(50,0,255),(255,0,255),(255,0,255),]
#not a bad spectrum[(255,30,30),(255,0,0),(255,60,0),(255,255,0),(80,255,0),(0,255,0),(0,255,30),(0,155,255),(0,0,255),(50,0,255),(255,0,255),(255,0,255),]
-# a a# b c c# d d# e f f# g g#
-picked_hues=[(255,0,0),(255,30,30),(255,60,0),(255,255,0),(255,255,30),(0,255,0),(80,220,10),(0,155,255),(0,0,255),(50,0,255),(255,0,255),(255,255,255)]
-brightness=0.1
+# a a# b c c# d d# e f f# g g#
+#rainbow syn hues
+# picked_hues=[(255,000,000),(255,030,030),(255,060,00),(255,255,000),(255,255,030),(000,255,000),(080,220,010),(000,155,255),(000,000,255),(050,000,255),(255,000,255),(255,255,255)]
+
+#plasma syn hues
+#calculated with the clb.generalised_color_LUT then manually adding sharps, then just full manual, sigh
+#bump for brightness of sharp/flat notes
+
+#full manual#picked_hues=[(0,0,255),(0,155,255),(050,000,255), (255,015,080), (255,000,000), (255,077,000), (255,201,000), (255,255,028), (255,255,237)]
+brightness=0.4
scaled_hues=[tuple(int(x*brightness) for x in t) for t in picked_hues]
print(scaled_hues)
diff --git a/software/micropython/utils/LED_test_flash_all.py b/software/micropython/utils/LED_test_flash_all.py
index aa2db78..d50bd92 100644
--- a/software/micropython/utils/LED_test_flash_all.py
+++ b/software/micropython/utils/LED_test_flash_all.py
@@ -14,7 +14,7 @@ def set_all_leds(color):
np.write()
while True:
- set_all_leds((55, 0, 0)) # Set all LEDs to red (R, G, B)
+ set_all_leds((0, 55, 0)) # Set all LEDs to red (R, G, B)
sleep(1)
set_all_leds((0, 0, 0)) # Turn off all LEDs
sleep(1)
\ No newline at end of file
diff --git a/software/micropython/utils/binned_indexes_of_tones_nearest_to_musical_notes.json b/software/micropython/utils/binned_indexes_of_tones_nearest_to_musical_notes.json
new file mode 100644
index 0000000..8d7fb9b
--- /dev/null
+++ b/software/micropython/utils/binned_indexes_of_tones_nearest_to_musical_notes.json
@@ -0,0 +1,25 @@
+{"2": [[14, 15], [16, 17], [18, 19], [20, 21], [22, 24], [25, 27], [28, 30], [32, 33], [35, 38], [40, 42], [45, 47], [50, 53], [56, 60], [63, 67], [71, 75], [80, 84],
+[89, 95], [100, 106], [113, 119], [126, 134], [142, 150], [159, 169], [179, 189], [201, 213], [225, 239], [253, 268], [284, 301], [319, 338], [358, 379], [401, 425],
+[451, 477], [506, 536], [568, 601], [637, 675], [715, 758], [803, 851], [901, 955], [1011, 1072], [1135, 1203], [1274, 1350], [1430, 1515], [1606, 1701], [1802, 1909],
+[2023]],
+
+"6": [[14, 15, 16, 17, 18, 19], [20, 21, 22, 24, 25, 27], [28, 30, 32, 33, 35, 38], [40, 42, 45, 47, 50, 53], [56, 60, 63, 67, 71, 75], [80, 84, 89, 95, 100, 106],
+[113, 119, 126, 134, 142, 150], [159, 169, 179, 189, 201, 213], [225, 239, 253, 268, 284, 301], [319, 338, 358, 379, 401, 425], [451, 477, 506, 536, 568, 601],
+[637, 675, 715, 758, 803, 851], [901, 955, 1011, 1072, 1135, 1203], [1274, 1350, 1430, 1515, 1606, 1701], [1802, 1909, 2023]],
+
+"1": [[14], [15], [16], [17], [18], [19], [20], [21], [22], [24], [25], [27], [28], [30], [32], [33], [35], [38], [40], [42], [45], [47], [50], [53], [56], [60], [63],
+[67], [71], [75], [80], [84], [89], [95], [100], [106], [113], [119], [126], [134], [142], [150], [159], [169], [179], [189], [201], [213], [225], [239], [253], [268],
+[284], [301], [319], [338], [358], [379], [401], [425], [451], [477], [506], [536], [568], [601], [637], [675], [715], [758], [803], [851], [901], [955], [1011], [1072],
+[1135], [1203], [1274], [1350], [1430], [1515], [1606], [1701], [1802], [1909], [2023]],
+
+"12": [[14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 27], [28, 30, 32, 33, 35, 38, 40, 42, 45, 47, 50, 53], [56, 60, 63, 67, 71, 75, 80, 84, 89, 95, 100, 106],
+[113, 119, 126, 134, 142, 150, 159, 169, 179, 189, 201, 213], [225, 239, 253, 268, 284, 301, 319, 338, 358, 379, 401, 425],
+[451, 477, 506, 536, 568, 601, 637, 675, 715, 758, 803, 851], [901, 955, 1011, 1072, 1135, 1203, 1274, 1350, 1430, 1515, 1606, 1701], [1802, 1909, 2023]],
+
+"3": [[14, 15, 16], [17, 18, 19], [20, 21, 22], [24, 25, 27], [28, 30, 32], [33, 35, 38], [40, 42, 45], [47, 50, 53], [56, 60, 63], [67, 71, 75], [80, 84, 89],
+[95, 100, 106], [113, 119, 126], [134, 142, 150], [159, 169, 179], [189, 201, 213], [225, 239, 253], [268, 284, 301], [319, 338, 358], [379, 401, 425],
+[451, 477, 506], [536, 568, 601], [637, 675, 715], [758, 803, 851], [901, 955, 1011], [1072, 1135, 1203], [1274, 1350, 1430], [1515, 1606, 1701], [1802, 1909, 2023]],
+
+"4": [[14, 15, 16, 17], [18, 19, 20, 21], [22, 24, 25, 27], [28, 30, 32, 33], [35, 38, 40, 42], [45, 47, 50, 53], [56, 60, 63, 67], [71, 75, 80, 84], [89, 95, 100, 106],
+[113, 119, 126, 134], [142, 150, 159, 169], [179, 189, 201, 213], [225, 239, 253, 268], [284, 301, 319, 338], [358, 379, 401, 425], [451, 477, 506, 536], [568, 601, 637, 675],
+[715, 758, 803, 851], [901, 955, 1011, 1072], [1135, 1203, 1274, 1350], [1430, 1515, 1606, 1701], [1802, 1909, 2023]]}
\ No newline at end of file
diff --git a/software/micropython/utils/binned_indexes_represent_which_musical_tones.json b/software/micropython/utils/binned_indexes_represent_which_musical_tones.json
new file mode 100644
index 0000000..8962ad4
--- /dev/null
+++ b/software/micropython/utils/binned_indexes_represent_which_musical_tones.json
@@ -0,0 +1 @@
+{"2": [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], [15, 16], [17, 18], [19, 20], [21, 22], [23, 24], [25, 26], [27, 28], [29, 30], [31, 32], [33, 34], [35, 36], [37, 38], [39, 40], [41, 42], [43, 44], [45, 46], [47, 48], [49, 50], [51, 52], [53, 54], [55, 56], [57, 58], [59, 60], [61, 62], [63, 64], [65, 66], [67, 68], [69, 70], [71, 72], [73, 74], [75, 76], [77, 78], [79, 80], [81, 82], [83, 84], [85, 86], [87]], "6": [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12], [13, 14, 15, 16, 17, 18], [19, 20, 21, 22, 23, 24], [25, 26, 27, 28, 29, 30], [31, 32, 33, 34, 35, 36], [37, 38, 39, 40, 41, 42], [43, 44, 45, 46, 47, 48], [49, 50, 51, 52, 53, 54], [55, 56, 57, 58, 59, 60], [61, 62, 63, 64, 65, 66], [67, 68, 69, 70, 71, 72], [73, 74, 75, 76, 77, 78], [79, 80, 81, 82, 83, 84], [85, 86, 87]], "1": [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], [25], [26], [27], [28], [29], [30], [31], [32], [33], [34], [35], [36], [37], [38], [39], [40], [41], [42], [43], [44], [45], [46], [47], [48], [49], [50], [51], [52], [53], [54], [55], [56], [57], [58], [59], [60], [61], [62], [63], [64], [65], [66], [67], [68], [69], [70], [71], [72], [73], [74], [75], [76], [77], [78], [79], [80], [81], [82], [83], [84], [85], [86], [87]], "12": [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36], [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48], [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60], [61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], [73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84], [85, 86, 87]], "3": [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20, 21], [22, 23, 24], [25, 26, 27], [28, 29, 30], [31, 32, 33], [34, 35, 36], [37, 38, 39], [40, 41, 42], [43, 44, 45], [46, 47, 48], [49, 50, 51], [52, 53, 54], [55, 56, 57], [58, 59, 60], [61, 62, 63], [64, 65, 66], [67, 68, 69], [70, 71, 72], [73, 74, 75], [76, 77, 78], [79, 80, 81], [82, 83, 84], [85, 86, 87]], "4": [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24], [25, 26, 27, 28], [29, 30, 31, 32], [33, 34, 35, 36], [37, 38, 39, 40], [41, 42, 43, 44], [45, 46, 47, 48], [49, 50, 51, 52], [53, 54, 55, 56], [57, 58, 59, 60], [61, 62, 63, 64], [65, 66, 67, 68], [69, 70, 71, 72], [73, 74, 75, 76], [77, 78, 79, 80], [81, 82, 83, 84], [85, 86, 87]]}
\ No newline at end of file
diff --git a/software/micropython/utils/border_calculator.py b/software/micropython/utils/border_calculator.py
index 4349fae..9c1ff9c 100644
--- a/software/micropython/utils/border_calculator.py
+++ b/software/micropython/utils/border_calculator.py
@@ -12,14 +12,14 @@
TUNING_A4_HZ=440.
BINS_PER_OCTAVE=2
-class PrecomputedValues:
+class PrecomputedBorders:
def __init__(self, filename):
self.filename = filename
self.data = {}
# Calculate the defined frequencies of the musical notes
#were initialized with 1.,180., maybe important, and yes, it sure was! borked the calculation of the frequencies, amazing
- self.notes=np.arange(0.,108.)# must be a multiple of 12, this range determines how many notes are stored in memory and are accessed by the spectrogram
+ self.notes=np.arange(1.,87.+1.)# must be a multiple of 12, this range determines how many notes are stored in memory and are accessed by the spectrogram
self.note_frequencies=TUNING_A4_HZ*(2**((self.notes-49)/12))
# print(self.note_frequencies)
@@ -28,8 +28,8 @@ def __init__(self, filename):
# For the basic options inside divisions of 12, make a list
self.notes_per_led_options=[1,2,3,4,6,12]
- self.length_of_leds=13 #actually needs to be number of leds+1, due to how the note border finding/zipping function organizes borders
- self.start_note=15
+ self.length_of_leds=13 #actually needs to be number of leds+1, due to how the note border finding/zipping function organizes halfways
+ self.start_note=13 #A1, 55Hz
def compute_and_save(self, compute_function):
@@ -59,63 +59,88 @@ def get(self, key, default=None):
# print(self.data.get(key))
return self.data.get(key)
-# Example usage
-def example_computation():
- """Example function to compute some values"""
- result = {}
- # Compute some expensive calculations
- for i in range(100):
- result[str(i)] = i * i
- return result
-
-
def computation(self):
result = {}
+
+ #find the halfway cross overs , making sure all bins are longer than 1 (not [14,14]), because of how np.sum ommits the last index, and does this: np.sum(values[14:14])=0
+ halfways=calculate_led_note_halfways(self)
+
+ #crossover_indexes = find_upper_fft_tone_indexes(self.tones,halfways)
+ crossover_indexes = [14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 49, 52, 55, 58, 62, 66, 69, 74, 78, 83, 87, 93, 98, 104, 110, 116, 123, 131, 138, 147, 155, 165, 174, 185, 196, 207, 219, 232, 246, 261, 276, 293, 310, 329, 348, 369, 391, 414, 438, 464, 492, 521, 552, 585, 620, 657, 696, 737, 781, 827, 876, 928, 984, 1042, 1104, 1170, 1239, 1313, 1391, 1473, 1561, 1654, 1752, 1856, 1967]
+
+ #TODO: fix for the specific case of 3 notes per pix. Just added it manually.
#populate json object with a result for each note resolution setting
- for notes_per_pix in self.notes_per_led_options:
- result[str(notes_per_pix)]=calculate_led_note_borders(self, notes_per_pix)
- return result
+ for notes_per_led in self.notes_per_led_options:
+ result[str(notes_per_led)]=[[crossover_indexes[i],crossover_indexes[i+notes_per_led]] for i in range(0,len(crossover_indexes)-notes_per_led,notes_per_led)]#segment the calculated above list
+
+ # Add the last bin with all remaining elements (thank you sonnet 4.5)
+ last_start = (len(crossover_indexes) // notes_per_led) * notes_per_led
+ print("last start:", last_start)
+ if last_start < len(crossover_indexes):
+ print('definitely scooped that last bit')
+ result[str(notes_per_led)].append([crossover_indexes[last_start], crossover_indexes[-1]])
+
+ print("list of halfway tone indexes for ", notes_per_led," notes per LED: ", result[str(notes_per_led)])
+ print('\n')
+ return result
-def calculate_led_note_borders(self, notes_per_led):
+def calculate_led_note_halfways(self):
# This creates a border exactly on a note. What is needed is a border halfway between one note and the next.
# I have frequencies, I need to know what note is at the border, index the next note, and take the middle
- #borders=note_frequencies[start_note::notes_per_led]
- #borders=np.zeros(self.length_of_leds) #creates 13 buckets to operate on, which is incorrect, I need all possible buckets at what ever resolution is set
- borders=np.zeros(int(len(self.notes)/notes_per_led))
- print("number of borders in ", notes_per_led, "is: ", len(borders))
- for i in range(len(borders)):
+ halfways=np.zeros(len(self.notes)-1)
+
+ for i in range(len(halfways)):
try:
- #by quirk of indexing, the start index is below the one of actual interest, e.g. G#0 is '0', not A0
- start_=self.note_frequencies[i*notes_per_led]
- next_=self.note_frequencies[(i*notes_per_led)+1]
- border_freq=start_+(next_-start_)/2
- borders[i]=border_freq
+ start_=self.note_frequencies[i]
+ next_=self.note_frequencies[i+1]
+
+ #find the frequency halfway between the two notes on the border
+ border_freq=(start_+next_)/2
+ halfways[i]=border_freq
except Exception as e:
print("Exception: ", e, "for index:", i)
break
+
+ return halfways
+
+#search sorted array for the two indexes that have the theoretical halfway points for musical notes. Determine which fft tone is nearest, return the index of that tone.
+def find_upper_fft_tone_indexes(tones, note_frequencies):
+ upper_tone_indexes=[]
+ note_index=0
+ for i in range(len(tones)-1):
+ try:
+ print("frequency: ",note_frequencies[note_index]," bottom match: ",tones[i]," top match: ", tones[i+1])
+ except Exception as e:
+ print(e)
+ break
+
+ if (note_frequencies[note_index](tones[i+1]-note_frequencies[note_index]):
+ upper_match_index=i+1
+ elif (tones[i]==note_frequencies[note_index]):
+ upper_match_index=i+1
+ elif (tones[i+1]==note_frequencies[note_index]):
+ upper_match_index=i+1
+ upper_tone_indexes.append(upper_match_index)
+ print("upper_match",upper_match_index)
+ note_index+=1
- result_fft_bins=calculate_fft_bin_boundaries(self, borders)
- return result_fft_bins
-# return borders #uncomment this and comment out above, to see what the frequency halfway between two notes is, e.g. between G# and A
+ print("upper tone indexes: ",upper_tone_indexes)
+ return upper_tone_indexes
-def calculate_fft_bin_boundaries(self, borders):
- crossovers = []
- # Loop through the fixed note interval array, finding the index where that boundary is crossed, storing that index and its corresponding frequency in a tuple, appending to a list
- # this is with respect to the mic sampling parameters, again.
- for i in range(len(self.tones) - 1):
- for boundary in borders:
- if self.tones[i] <= boundary < self.tones[i + 1]:
- crossovers.append((i, self.tones[i]))
- ##print(crossovers)
- self.fft_ranges=[(tup[0],crossovers[i+1][0]-1) for i, tup in enumerate(crossovers[:-1])] #subtract -1 from the second item in the tuple avoid doubling up values at the seams
- print(self.fft_ranges)
- print(type(self.fft_ranges))
- return(self.fft_ranges)
# Create instance and save computed values
-# storage = PrecomputedValues('test_speedup_redo_values.json')
+# storage = PrecomputedBorders('utils/trying for better divisions between A and Asharp.json')
# storage.compute_and_save(computation)
#####COMMENT OUT WHEN DONE OR WILL HANG AND RERUN WHEN HITTING MAIN#####
diff --git a/software/micropython/utils/huelearner.py b/software/micropython/utils/huelearner.py
index 0e27b52..35fa798 100644
--- a/software/micropython/utils/huelearner.py
+++ b/software/micropython/utils/huelearner.py
@@ -21,7 +21,9 @@ async def start(self):
for j in range(0,3):#for each LED strip
for i in range(0,self.num_leds):#for each LED on the strip
- #0-65535 (16bit)
+ #0-65535 (16bit)
+ #(index1,index2,hue,sat.brightness)
+
await self.leds.show_hsv(j,i,int((self.base_hue+self.hue_delta*i)+(j*self.num_leds*self.hue_delta))%65535,255,5)#j+1 because otherwise the first row would be all one colour
await self.leds.write(j)
await asyncio.sleep_ms(0) # Yield to other tasks
diff --git a/software/micropython/utils/individual_LED_test.py b/software/micropython/utils/individual_LED_test.py
index 40febe0..c68e0fd 100644
--- a/software/micropython/utils/individual_LED_test.py
+++ b/software/micropython/utils/individual_LED_test.py
@@ -3,10 +3,10 @@
from time import sleep
# Set up the NeoPixel on pin 15 with 8 LEDs (adjust pin and number of LEDs)
-pin = Pin(10, Pin.OUT)
+pin = Pin(8, Pin.OUT)
num_leds = 36
np = neopixel.NeoPixel(pin, num_leds)
-np[1] = (0,0,40)
+np[1] = (0,55,0)
np.write()
diff --git a/software/micropython/utils/menu_calculator.py b/software/micropython/utils/menu_calculator.py
index 92ab0db..80ff947 100644
--- a/software/micropython/utils/menu_calculator.py
+++ b/software/micropython/utils/menu_calculator.py
@@ -1,6 +1,6 @@
#claude gave me this to start with
import json
-from utils.border_calculator import PrecomputedValues
+from utils.border_calculator import PrecomputedBorders
class PrecomputedMenu:
def __init__(self, filename):
@@ -9,7 +9,7 @@ def __init__(self, filename):
self.notes_per_led_options=[1,2,3,4,6,12]
#get the corresponsing lengths of the corresponding FFT resolution windows
- self.precomputed_borders=PrecomputedValues("utils/test_speedup_redo_values.json")
+ self.precomputed_borders=PrecomputedBorders("utils/test_speedup_redo_values.json")
self.precomputed_borders.load()
self.fft_bin_lengths={}
diff --git a/software/micropython/utils/nearest_tone_index_calculator.py b/software/micropython/utils/nearest_tone_index_calculator.py
new file mode 100644
index 0000000..6eb4c33
--- /dev/null
+++ b/software/micropython/utils/nearest_tone_index_calculator.py
@@ -0,0 +1,124 @@
+#claude gave me this to start with
+import json
+
+import math
+from ulab import numpy as np
+
+#these parameters determine the resolution of the fft
+SAMPLE_RATE = 8000 # Hz
+SAMPLE_SIZE = 16
+SAMPLE_COUNT = 4096 #8192 #
+FREQUENCY_RESOLUTION=SAMPLE_RATE/SAMPLE_COUNT
+TUNING_A4_HZ=440.
+BINS_PER_OCTAVE=2
+
+class PrecomputedNearestTones:
+ def __init__(self, filename):
+ self.filename = filename
+ self.data = {}
+
+ # Calculate the defined frequencies of the musical notes
+ #were initialized with 1.,180., maybe important, and yes, it sure was! borked the calculation of the frequencies, amazing
+ self.notes=np.arange(1.,87.+1)# # This is the range of notes I can performantly resolve with the FFT settings
+ self.note_frequencies=TUNING_A4_HZ*(2**((self.notes-49)/12))
+ print("notes:",self.note_frequencies)
+ print(len(self.notes))
+
+
+ # Calculate the tones measured by the linear fft used presently
+ self.tones=FREQUENCY_RESOLUTION*np.arange(SAMPLE_COUNT//2)
+ print("tones:",self.tones)
+# print(self.note_frequencies)
+
+
+ # For the basic options inside divisions of 12, make a list
+ self.notes_per_led_options=[1,2,3,4,6,12]
+ self.length_of_leds=13 #actually needs to be number of leds+1, due to how the note border finding/zipping function organizes halfways
+ self.start_note=13 #A1, 55Hz
+
+
+ def compute_and_save(self, compute_function):
+ """
+ Compute values using the provided function and save to file
+ compute_function should return a dictionary of computed values
+ """
+ self.data = compute_function(self)
+
+ # Save to file
+ with open(self.filename, 'w') as f:
+ json.dump(self.data, f)
+
+ def load(self):
+ """Load precomputed values from file"""
+ try:
+ with open(self.filename, 'r') as f:
+ self.data = json.load(f)
+ return True
+ except OSError:
+ print(f"No precomputed values file found at {self.filename}")
+ return False
+
+ def get(self, key, default=None):
+ """Get a value by key"""
+ #return self.data.get(key, default)
+# print(self.data.get(key))
+ return self.data.get(key)
+
+
+def computation(self):
+ result = {}
+ #find the nearest fft tone's index for all desired notes:
+ nearest_tone_indexes=find_nearest_fft_tone_indexes(self.tones,self.note_frequencies)
+
+ #segment nearest tones per notes_per_led options.
+ #populate json object with a result for each note resolution setting
+ for notes_per_led in self.notes_per_led_options:
+ result[str(notes_per_led)]=[nearest_tone_indexes[i:i+notes_per_led] for i in range(0,len(self.note_frequencies),notes_per_led)]#segment the calculated above list
+ print("list of note indexes for ", notes_per_led," notes per LED: ", result[str(notes_per_led)])
+
+ return result
+
+#search sorted array for the two indexes that have the theoretical musical note between them. Determine which fft tone is nearest, return the index of that tone.
+def find_nearest_fft_tone_indexes(tones, note_frequencies):
+ nearest_tone_indexes=[]
+ note_index=0
+ for i in range(len(tones)-1):
+ try:
+ print("frequency: ",note_frequencies[note_index]," bottom match: ",tones[i]," top match: ", tones[i+1])
+ except Exception as e:
+ print(e)
+ break
+
+ if (note_frequencies[note_index](tones[i+1]-note_frequencies[note_index]):
+ closest_match_index=i+1
+ elif (tones[i]==note_frequencies[note_index]):
+ closest_match_index=i
+ elif (tones[i+1]==note_frequencies[note_index]):
+ closest_match_index=i+1
+ nearest_tone_indexes.append(closest_match_index)
+ print("nearest_match",closest_match_index)
+ note_index+=1
+
+ print("nearest tone indexes: ",nearest_tone_indexes)
+ return nearest_tone_indexes
+
+
+# Create instance and save computed values
+# storage = PrecomputedNearestTones('utils/binned_indexes_of_tones_nearest_to_musical_notes.json')
+# storage.compute_and_save(computation)
+#####COMMENT OUT WHEN DONE OR WILL HANG AND RERUN WHEN HITTING MAIN#####
+
+# # Later, in another program:
+# storage = PrecomputedValues('computed_values.json')
+# if storage.load():
+# value = storage.get('50') # Gets the precomputed value for key '50'
+# print(value)
\ No newline at end of file
diff --git a/software/micropython/utils/nearest_tone_index_represents.py b/software/micropython/utils/nearest_tone_index_represents.py
new file mode 100644
index 0000000..dfe3f0a
--- /dev/null
+++ b/software/micropython/utils/nearest_tone_index_represents.py
@@ -0,0 +1,88 @@
+#claude gave me this to start with
+import json
+
+import math
+from ulab import numpy as np
+
+#these parameters determine the resolution of the fft
+SAMPLE_RATE = 8000 # Hz
+SAMPLE_SIZE = 16
+SAMPLE_COUNT = 4096 #8192 #
+FREQUENCY_RESOLUTION=SAMPLE_RATE/SAMPLE_COUNT
+TUNING_A4_HZ=440.
+BINS_PER_OCTAVE=2
+
+class PrecomputedToneRepresentations:
+ def __init__(self, filename):
+ self.filename = filename
+ self.data = {}
+
+ # Calculate the defined frequencies of the musical notes
+ #were initialized with 1.,180., maybe important, and yes, it sure was! borked the calculation of the frequencies, amazing
+ self.notes=np.arange(1,87+1)# This is the range of notes I can performantly resolve with the FFT settings
+ self.note_frequencies=TUNING_A4_HZ*(2**((self.notes-49)/12))
+ print("notes:",self.note_frequencies)
+ print(len(self.notes))
+
+
+ # Calculate the tones measured by the linear fft used presently
+ self.tones=FREQUENCY_RESOLUTION*np.arange(SAMPLE_COUNT//2)
+ print("tones:",self.tones)
+# print(self.note_frequencies)
+
+
+ # For the basic options inside divisions of 12, make a list
+ self.notes_per_led_options=[1,2,3,4,6,12]
+ self.length_of_leds=13 #actually needs to be number of leds+1, due to how the note border finding/zipping function organizes halfways
+ self.start_note=13 #A1, 55Hz
+
+
+ def compute_and_save(self, compute_function):
+ """
+ Compute values using the provided function and save to file
+ compute_function should return a dictionary of computed values
+ """
+ self.data = compute_function(self)
+
+ # Save to file
+ with open(self.filename, 'w') as f:
+ json.dump(self.data, f)
+
+ def load(self):
+ """Load precomputed values from file"""
+ try:
+ with open(self.filename, 'r') as f:
+ self.data = json.load(f)
+ return True
+ except OSError:
+ print(f"No precomputed values file found at {self.filename}")
+ return False
+
+ def get(self, key, default=None):
+ """Get a value by key"""
+ #return self.data.get(key, default)
+# print(self.data.get(key))
+ return self.data.get(key)
+
+def computation(self):
+ result = {}
+
+ #segment nearest tones per notes_per_led options.
+ #populate json object with a result for each note resolution setting
+ for notes_per_led in self.notes_per_led_options:
+ result[str(notes_per_led)]=[list(self.notes[i:i+notes_per_led]) for i in range(0,len(self.notes),notes_per_led)]#segment the calculated above list
+ print("list of note indexes for ", notes_per_led," notes per LED: ", result[str(notes_per_led)])
+
+ return result
+
+
+# Create instance and save computed values
+# storage = PrecomputedToneRepresentations('utils/binned_indexes_represent_which_musical_tones.json')
+# storage.compute_and_save(computation)
+#####COMMENT OUT WHEN DONE OR WILL HANG AND RERUN WHEN HITTING MAIN#####
+
+# # Later, in another program:
+# storage = PrecomputedValues('computed_values.json')
+# if storage.load():
+# value = storage.get('50') # Gets the precomputed value for key '50'
+# print(value)
\ No newline at end of file
diff --git a/software/micropython/utils/precomputed_octave_display.json b/software/micropython/utils/precomputed_octave_display.json
index 8de0d88..1b05963 100644
--- a/software/micropython/utils/precomputed_octave_display.json
+++ b/software/micropython/utils/precomputed_octave_display.json
@@ -1 +1,12 @@
-{"2": [0, -1, -1, -1, -1, -1, 900, -1, -1, -1, -1, -1, 1800, -1, -1, -1, -1, -1, 2700, -1, -1, -1, -1, -1, 3600, -1, -1, -1, -1, -1, 4500, -1, -1, -1, -1, -1, 5400, -1, -1, -1, -1, -1, 6300], "6": [0, -1, 900, -1, 1800, -1, 2700, -1, 3600, -1, 4500, -1, 5400, -1], "1": [0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 900, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1800, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2700, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3600, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4500, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5400, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6300, -1, -1], "12": [0, 900, 1800, 2700, 3600, 4500, 5400], "3": [0, -1, -1, -1, 900, -1, -1, -1, 1800, -1, -1, -1, 2700, -1, -1, -1, 3600, -1, -1, -1, 4500, -1, -1, -1, 5400, -1, -1, -1], "4": [0, -1, -1, 900, -1, -1, 1800, -1, -1, 2700, -1, -1, 3600, -1, -1, 4500, -1, -1, 5400, -1, -1]}
\ No newline at end of file
+{"2": [0, -1, -1, -1, -1, -1, 900, -1, -1, -1, -1, -1, 1800, -1, -1, -1, -1, -1, 2700, -1, -1, -1, -1, -1, 3600, -1, -1, -1, -1, -1, 4500, -1, -1, -1, -1, -1, 5400,
+-1, -1, -1, -1, -1, 6300], "6": [0, -1, 900, -1, 1800, -1, 2700, -1, 3600, -1, 4500, -1, 5400, -1],
+
+"1": [0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 900, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1800, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2700,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3600, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4500, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5400,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6300, -1, -1],
+
+"12": [0, 900, 1800, 2700, 3600, 4500, 5400],
+
+"3": [0, -1, -1, -1, 900, -1, -1, -1, 1800, -1, -1, -1, 2700, -1, -1, -1, 3600, -1, -1, -1, 4500, -1, -1, -1, 5400,-1, -1, -1],
+
+"4": [0, -1, -1, 900, -1, -1, 1800, -1, -1, 2700, -1, -1, 3600, -1, -1, 4500, -1, -1, 5400, -1, -1]}
\ No newline at end of file
diff --git a/software/micropython/utils/test_speedup_redo_values.json b/software/micropython/utils/test_speedup_redo_values.json
index feeb8dc..4ba38d4 100644
--- a/software/micropython/utils/test_speedup_redo_values.json
+++ b/software/micropython/utils/test_speedup_redo_values.json
@@ -1,5 +1,16 @@
{"2": [[13, 14], [15, 16], [17, 18], [19, 20], [21, 23], [24, 26], [27, 29], [30, 33], [34, 37], [38, 42], [43, 47], [48, 53], [54, 60], [61, 67], [68, 76], [77, 85], [86, 96], [97, 108], [109, 121], [122, 136], [137, 153], [154, 172], [173, 194], [195, 217], [218, 244], [245, 274], [275, 308], [309, 346], [347, 389], [390, 436], [437, 490], [491, 550], [551, 618], [619, 694], [695, 779], [780, 874], [875, 982], [983, 1102], [1103, 1237], [1238, 1389], [1390, 1559], [1560, 1750], [1751, 1965]],
"6": [[13, 18], [19, 26], [27, 37], [38, 53], [54, 76], [77, 108], [109, 153], [154, 217], [218, 308], [309, 436], [437, 618], [619, 874], [875, 1237], [1238, 1750]],
-"1": [[0, 12], [13, 13], [14, 14], [15, 15], [16, 16], [17, 17], [18, 18], [19, 19], [20, 20], [21, 22], [23, 23], [24, 24], [25, 26], [27, 27], [28, 29], [30, 31], [32, 33], [34, 35], [36, 37], [38, 40], [41, 42], [43, 45], [46, 47], [48, 50], [51, 53], [54, 56], [57, 60], [61, 64], [65, 67], [68, 72], [73, 76], [77, 81], [82, 85], [86, 91], [92, 96], [97, 102], [103, 108], [109, 114], [115, 121], [122, 129], [130, 136], [137, 145], [146, 153], [154, 163], [164, 172], [173, 183], [184, 194], [195, 205], [206, 217], [218, 230], [231, 244], [245, 259], [260, 274], [275, 291], [292, 308], [309, 327], [328, 346], [347, 367], [368, 389], [390, 412], [413, 436], [437, 462], [463, 490], [491, 519], [520, 550], [551, 583], [584, 618], [619, 655], [656, 694], [695, 735], [736, 779], [780, 825], [826, 874], [875, 926], [927, 982], [983, 1040], [1041, 1102], [1103, 1168], [1169, 1237], [1238, 1311], [1312, 1389], [1390, 1471], [1472, 1559], [1560, 1652], [1653, 1750], [1751, 1854], [1855, 1965]], "12": [[13, 26], [27, 53], [54, 108], [109, 217], [218, 436], [437, 874], [875, 1750]],
+
+"1": [
+[13, 13], [14, 14], [15, 15], [16, 16], [17, 17], [18, 18], [19, 19], [20, 20], [21, 22], [23, 23], [24, 24], [25, 26],
+[27, 27], [28, 29], [30, 31], [32, 33], [34, 35], [36, 37], [38, 40], [41, 42], [43, 45], [46, 47], [48, 50], [51, 53],
+[55, 57], [58, 61], [62, 65], [66, 68], [69, 73], [74, 77], [78, 82], [83, 86], [87, 91], [92, 97], [98, 103], [104, 109],
+[110, 120], [115, 121], [122, 129], [130, 136], [137, 145], [146, 153], [154, 163], [164, 172], [173, 183], [184, 194], [195, 205], [206, 217],
+[218, 230], [231, 244], [245, 259], [260, 274], [275, 291], [292, 308], [309, 327], [328, 346], [347, 367], [368, 389], [390, 412], [413, 436],
+[437, 462], [463, 490], [491, 519], [520, 550], [551, 583], [584, 618], [619, 655], [656, 694], [695, 735], [736, 779], [780, 825], [826, 874],
+[875, 926], [927, 982], [983, 1040], [1041, 1102], [1103, 1168], [1169, 1237], [1238, 1311], [1312, 1389], [1390, 1471], [1472, 1559], [1560, 1652], [1653, 1750],
+[1751, 1854], [1855, 1965]],
+
+"12": [[13, 26], [27, 53], [54, 108], [109, 217], [218, 436], [437, 874], [875, 1750]],
"3": [[13, 15], [16, 18], [19, 22], [23, 26], [27, 31], [32, 37], [38, 45], [46, 53], [54, 64], [65, 76], [77, 91], [92, 108], [109, 129], [130, 153], [154, 183], [184, 217], [218, 259], [260, 308], [309, 367], [368, 436], [437, 519], [520, 618], [619, 735], [736, 874], [875, 1040], [1041, 1237], [1238, 1471], [1472, 1750]],
"4": [[13, 16], [17, 20], [21, 26], [27, 33], [34, 42], [43, 53], [54, 67], [68, 85], [86, 108], [109, 136], [137, 172], [173, 217], [218, 274], [275, 346], [347, 436], [437, 550], [551, 694], [695, 874], [875, 1102], [1103, 1389], [1390, 1750]]}
\ No newline at end of file
diff --git a/software/micropython/utils/trying for better divisions between A and Asharp.json b/software/micropython/utils/trying for better divisions between A and Asharp.json
new file mode 100644
index 0000000..ff67eec
--- /dev/null
+++ b/software/micropython/utils/trying for better divisions between A and Asharp.json
@@ -0,0 +1,9 @@
+{"2": [[14, 16], [16, 18], [18, 20], [20, 22], [22, 25], [25, 28], [28, 31], [31, 35], [35, 39], [39, 44], [44, 49], [49, 55], [55, 62], [62, 69], [69, 78], [78, 87], [87, 98], [98, 110], [110, 123], [123, 138], [138, 155], [155, 174], [174, 196], [196, 219], [219, 246], [246, 276], [276, 310], [310, 348], [348, 391], [391, 438], [438, 492], [492, 552], [552, 620], [620, 696], [696, 781], [781, 876], [876, 984], [984, 1104], [1104, 1239], [1239, 1391], [1391, 1561], [1561, 1752], [1752, 1967]],
+
+"6": [[14, 20], [20, 28], [28, 39], [39, 55], [55, 78], [78, 110], [110, 155], [155, 219], [219, 310], [310, 438], [438, 620], [620, 876], [876, 1239], [1239, 1752]],
+
+"1": [[14, 15], [15, 16], [16, 17], [17, 18], [18, 19], [19, 20], [20, 21], [21, 22], [22, 24], [24, 25], [25, 26], [26, 28], [28, 29], [29, 31], [31, 33], [33, 35], [35, 37], [37, 39], [39, 42], [42, 44], [44, 47], [47, 49], [49, 52], [52, 55], [55, 58], [58, 62], [62, 66], [66, 69], [69, 74], [74, 78], [78, 83], [83, 87], [87, 93], [93, 98], [98, 104], [104, 110], [110, 116], [116, 123], [123, 131], [131, 138], [138, 147], [147, 155], [155, 165], [165, 174], [174, 185], [185, 196], [196, 207], [207, 219], [219, 232], [232, 246], [246, 261], [261, 276], [276, 293], [293, 310], [310, 329], [329, 348], [348, 369], [369, 391], [391, 414], [414, 438], [438, 464], [464, 492], [492, 521], [521, 552], [552, 585], [585, 620], [620, 657], [657, 696], [696, 737], [737, 781], [781, 827], [827, 876], [876, 928], [928, 984], [984, 1042], [1042, 1104], [1104, 1170], [1170, 1239], [1239, 1313], [1313, 1391], [1391, 1473], [1473, 1561], [1561, 1654], [1654, 1752], [1752, 1856], [1856, 1967]],
+
+"12": [[14, 28], [28, 55], [55, 110], [110, 219], [219, 438], [438, 876], [876, 1752]],
+
+"3": [[14, 17], [17, 20], [20, 24], [24, 28], [28, 33], [33, 39], [39, 47], [47, 55], [55, 66], [66, 78], [78, 93], [93, 110], [110, 131], [131, 155], [155, 185], [185, 219], [219, 261], [261, 310], [310, 369], [369, 438], [438, 521], [521, 620], [620, 737], [737, 876], [876, 1042], [1042, 1239], [1239, 1473], [1473, 1752]], "4": [[14, 18], [18, 22], [22, 28], [28, 35], [35, 44], [44, 55], [55, 69], [69, 87], [87, 110], [110, 138], [138, 174], [174, 219], [219, 276], [276, 348], [348, 438], [438, 552], [552, 696], [696, 876], [876, 1104], [1104, 1391], [1391, 1752]]}
\ No newline at end of file