init: scorometer from poc
This commit is contained in:
13
scorometer/chroma_case/Note.py
Normal file
13
scorometer/chroma_case/Note.py
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
class Note:
|
||||
def __init__(self, start_time, data) -> None:
|
||||
|
||||
self.__start_time = start_time
|
||||
self.__data = data
|
||||
|
||||
def get_start_time(self):
|
||||
return self.__start_time
|
||||
|
||||
def get_data(self):
|
||||
return self.__data
|
||||
34
scorometer/chroma_case/Partition.py
Normal file
34
scorometer/chroma_case/Partition.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import asyncio, datetime
|
||||
from typing import Callable
|
||||
|
||||
from .Note import Note
|
||||
|
||||
async def wait_until(dt):
|
||||
# sleep until the specified datetime
|
||||
now = datetime.datetime.now()
|
||||
await asyncio.sleep((dt - now).total_seconds())
|
||||
|
||||
async def run_at(dt, coro):
|
||||
await wait_until(dt)
|
||||
return await coro
|
||||
|
||||
class Partition:
|
||||
|
||||
def __init__(self, name:str, notes:list[Note]) -> None:
|
||||
|
||||
self.__name = name
|
||||
self.__notes = notes
|
||||
|
||||
async def play(self, output_lambda:Callable[[object], None]):
|
||||
now = datetime.datetime.now()
|
||||
tasks_to_wait = []
|
||||
for note in self.__notes:
|
||||
tasks_to_wait.append(
|
||||
asyncio.create_task(
|
||||
run_at(
|
||||
now + datetime.timedelta(milliseconds= note.get_start_time()),
|
||||
output_lambda(note.get_data())
|
||||
)
|
||||
)
|
||||
)
|
||||
await asyncio.wait(tasks_to_wait)
|
||||
0
scorometer/chroma_case/__init__.py
Normal file
0
scorometer/chroma_case/__init__.py
Normal file
5
scorometer/launch.sh
Executable file
5
scorometer/launch.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/zsh
|
||||
|
||||
sudo python3 main.py $1 &!
|
||||
sleep 3.5
|
||||
./tester.py $1 $2
|
||||
56
scorometer/leds.py
Executable file
56
scorometer/leds.py
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import board
|
||||
import neopixel
|
||||
import time
|
||||
import sys
|
||||
import asyncio
|
||||
|
||||
colorToFill = (0, 0, 0)
|
||||
pixels = neopixel.NeoPIxel(board.D18, 20, brightness=0.01)
|
||||
|
||||
notePixels = { 'si': [0, 1],
|
||||
'la#': [2, 3],
|
||||
'la': [4, 5],
|
||||
'sol#':[6],
|
||||
'sol':[7, 8, 9],
|
||||
'fa#':[10],
|
||||
'fa':[11, 12, 13],
|
||||
'mi':[14, 15, 16],
|
||||
're#':[17],
|
||||
're':[18, 19],
|
||||
'do#':[],
|
||||
'do':[]}
|
||||
|
||||
def playNote(color, secondsToStay, pixelsToFill):
|
||||
for pixelIndex in pixelsToFill:
|
||||
pixels[pixelIndex] = color
|
||||
time.sleep(secondsToStay)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def launchMusic(noteList):
|
||||
|
||||
pixels.fill(0,0,0)
|
||||
pixels.write()
|
||||
|
||||
for notes, tempo in noteList:
|
||||
for note in notes:
|
||||
playNote((255, 0, 0), tempo, notePixels[note.lower()])
|
||||
pixels.fill(colorToFill)
|
||||
pixels.write()
|
||||
|
||||
music = [
|
||||
(['sol'], 1),
|
||||
(['sol'], 1),
|
||||
(['sol'], 1),
|
||||
(['re#'], 1),
|
||||
(['la#'], 0.5),
|
||||
(['sol'], 0.5),
|
||||
(['re#'], 1),
|
||||
(['la#'], 0.5),
|
||||
]
|
||||
|
||||
launchMusic(music)
|
||||
166
scorometer/main.py
Normal file
166
scorometer/main.py
Normal file
@@ -0,0 +1,166 @@
|
||||
from xmlrpc.client import TRANSPORT_ERROR
|
||||
from chroma_case.Partition import Partition
|
||||
from chroma_case.Note import Note
|
||||
import asyncio
|
||||
import sys
|
||||
from mido import MidiFile
|
||||
|
||||
import board, neopixel
|
||||
|
||||
# on octave is 12
|
||||
RATIO = float(sys.argv[2] if len(sys.argv) > 2 else 1)
|
||||
OCTAVE = 5
|
||||
OCTAVE_AMOUNT_KEYS = 12
|
||||
TRANSPOSE_AMOUNT = OCTAVE_AMOUNT_KEYS * OCTAVE
|
||||
|
||||
pixels = neopixel.NeoPixel(board.D18, 20, brightness=0.01)
|
||||
|
||||
notePixels = { 'si': [19],
|
||||
'la#': [18],
|
||||
'la': [17],
|
||||
'sol#':[15],
|
||||
'sol':[13],
|
||||
'fa#':[10],
|
||||
'fa':[9],
|
||||
'mi':[6],
|
||||
're#':[5],
|
||||
're':[3],
|
||||
'do#':[1],
|
||||
'do':[0]}
|
||||
|
||||
|
||||
def hue_to_rgb(t1, t2, hue):
|
||||
if hue < 0: hue += 6
|
||||
if hue >= 6: hue -= 6
|
||||
if hue < 1: return (t2 - t1) * hue + t1
|
||||
if hue < 3: return t2
|
||||
if hue < 4: return (t2 - t1) * (4 - hue) + t1
|
||||
return t1
|
||||
|
||||
def hsl_to_rgb(hue, sat, light):
|
||||
hue /= 60
|
||||
if light <= 0.5:
|
||||
t2 = light * (sat + 1)
|
||||
else:
|
||||
t2 = light + sat - (light * sat)
|
||||
t1 = light * 2 - t2
|
||||
|
||||
r = hue_to_rgb(t1, t2, hue + 2) * 255
|
||||
g = hue_to_rgb(t1, t2, hue) * 255
|
||||
b = hue_to_rgb(t1, t2, hue - 2) * 255
|
||||
return [round(r), round(g), round(b)]
|
||||
|
||||
async def to_chroma_case(data):
|
||||
global pixels
|
||||
|
||||
hsl_starting_color = [100, 100, 50]
|
||||
|
||||
colored_pixels = notePixels[data["key"].lower()]
|
||||
#if "announce" in data:
|
||||
c = data["color"]
|
||||
"""for i in range(5):
|
||||
for pixelId in colored_pixels:
|
||||
pixels[pixelId] = (c[0], int(c[1] * tmp), c[2])
|
||||
tmp -= 0.2
|
||||
await asyncio.sleep(data["duration"] / (5 * 1000))"""
|
||||
"""for i in range(11):
|
||||
for pixelId in colored_pixels:
|
||||
pixels[pixelId] = hsl_to_rgb(hsl_starting_color[0], hsl_starting_color[1], hsl_starting_color[2])
|
||||
hsl_starting_color[2] += 0.01
|
||||
await asyncio.sleep(0.01)"""
|
||||
for pixelId in colored_pixels:
|
||||
pixels[pixelId] = data["color"]
|
||||
await asyncio.sleep(data['duration'] / 1000)
|
||||
for pixelId in colored_pixels:
|
||||
pixels[pixelId] = 0
|
||||
|
||||
|
||||
async def printing(data):
|
||||
print(f"key: {data['key']}, c:{data['color']} for {data['duration'] / 1000}s, time: {data['time']}")
|
||||
await asyncio.sleep(data['duration'] / 1000)
|
||||
print(f"end of {data['key']}")
|
||||
|
||||
|
||||
def midi_key_my_key(midi_key):
|
||||
keys = list(notePixels.keys())
|
||||
|
||||
keys.reverse()
|
||||
|
||||
key_index = midi_key - TRANSPOSE_AMOUNT
|
||||
if key_index >= len(keys):
|
||||
print("key out of leb barre", key_index)
|
||||
return "no_key"
|
||||
|
||||
return keys[key_index]
|
||||
|
||||
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
default_duration = 900
|
||||
default_color = (255, 0, 0)
|
||||
|
||||
notes = []
|
||||
# notes will start to play at 3500 ms (colors at the start takes this amount of time)
|
||||
s = 3500
|
||||
|
||||
notes_on = {}
|
||||
prev_note_on = {}
|
||||
note_color = {}
|
||||
|
||||
for msg in MidiFile(sys.argv[1]):
|
||||
d = msg.dict()
|
||||
print(msg, s)
|
||||
s += d['time'] * 1000 * RATIO
|
||||
|
||||
if d["type"] == "note_on":
|
||||
prev_note_on[d["note"]] = 0
|
||||
if d["note"] in notes_on:
|
||||
prev_note_on[d["note"]] = notes_on[d["note"]] # 500
|
||||
notes_on[d["note"]] = s # 0
|
||||
if d["note"] not in note_color.keys():
|
||||
note_color[d["note"]] = 1
|
||||
note_color[d["note"]] = not note_color[d["note"]]
|
||||
|
||||
if d["type"] == "note_off":
|
||||
#duration = s - notes_on[d["note"]]
|
||||
duration = s - notes_on[d["note"]]
|
||||
|
||||
"""notes.append(Note(
|
||||
s - min(s - prev_note_on[d["note"]], 500),
|
||||
{
|
||||
"duration": min(s - prev_note_on[d["note"]], 1000) / 2,
|
||||
"color": (255, 255, 0),
|
||||
"key": midi_key_my_key(d["note"]),
|
||||
"announce": True
|
||||
}
|
||||
))"""
|
||||
|
||||
note_start = notes_on[d["note"]]
|
||||
# time value is only used during debug
|
||||
notes.append(Note(note_start, {"time": note_start, "duration": duration - 10, "color": default_color if note_color[d["note"]] else (255, 100, 0), "key": midi_key_my_key(d["note"])}))
|
||||
notes_on[d["note"]] = s # 500
|
||||
|
||||
|
||||
|
||||
starting = []
|
||||
|
||||
for i in notePixels.keys():
|
||||
starting += [
|
||||
Note(000, {"duration": default_duration, "color": (255, 0, 0), "key": i, "time": 0}),
|
||||
Note(1000, {"duration": default_duration, "color": (255, 255, 0), "key": i, "time": 0}),
|
||||
Note(2000, {"duration": default_duration, "color": (0, 255, 0), "key": i, "time": 0}),
|
||||
]
|
||||
|
||||
p = Partition("my_partition",
|
||||
starting + notes
|
||||
)
|
||||
|
||||
await p.play(to_chroma_case)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(asyncio.run(main()))
|
||||
4
scorometer/midi.py
Normal file
4
scorometer/midi.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from mido import MidiFile
|
||||
|
||||
for msg in MidiFile('new_song_1.mid'):
|
||||
print(msg)
|
||||
7
scorometer/myscr
Executable file
7
scorometer/myscr
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
while true
|
||||
do
|
||||
sudo python main.py new_song_2.mid
|
||||
done
|
||||
|
||||
BIN
scorometer/partitions/20220128_095349.jpg
Normal file
BIN
scorometer/partitions/20220128_095349.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
BIN
scorometer/partitions/Believer.mid
Normal file
BIN
scorometer/partitions/Believer.mid
Normal file
Binary file not shown.
BIN
scorometer/partitions/Bella-Ciao.mid
Normal file
BIN
scorometer/partitions/Bella-Ciao.mid
Normal file
Binary file not shown.
BIN
scorometer/partitions/Game.mid
Normal file
BIN
scorometer/partitions/Game.mid
Normal file
Binary file not shown.
BIN
scorometer/partitions/Game2.mid
Normal file
BIN
scorometer/partitions/Game2.mid
Normal file
Binary file not shown.
BIN
scorometer/partitions/Tetris.mid
Normal file
BIN
scorometer/partitions/Tetris.mid
Normal file
Binary file not shown.
BIN
scorometer/partitions/clair-de-lune.midi
Normal file
BIN
scorometer/partitions/clair-de-lune.midi
Normal file
Binary file not shown.
BIN
scorometer/partitions/new_song_1.mid
Normal file
BIN
scorometer/partitions/new_song_1.mid
Normal file
Binary file not shown.
BIN
scorometer/partitions/new_song_2.mid
Normal file
BIN
scorometer/partitions/new_song_2.mid
Normal file
Binary file not shown.
2
scorometer/requirements.txt
Normal file
2
scorometer/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
mido
|
||||
pygame
|
||||
15
scorometer/test.py
Normal file
15
scorometer/test.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import asyncio
|
||||
|
||||
async def nested():
|
||||
return 42
|
||||
|
||||
async def main():
|
||||
# Schedule nested() to run soon concurrently
|
||||
# with "main()".
|
||||
task = asyncio.create_task(nested())
|
||||
|
||||
# "task" can now be used to cancel "nested()", or
|
||||
# can simply be awaited to wait until it is complete:
|
||||
await task
|
||||
|
||||
asyncio.run(main())
|
||||
146
scorometer/tester.py
Executable file
146
scorometer/tester.py
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
import pygame as pg
|
||||
from pygame.constants import KEYDOWN
|
||||
import pygame.midi
|
||||
from mido import MidiFile
|
||||
|
||||
# Status definitions
|
||||
TOUCH_DOWN = 144
|
||||
TOUCH_UP = 128
|
||||
|
||||
|
||||
class Key:
|
||||
def __init__(self, key: int, start: int, duration: int):
|
||||
self.key = key
|
||||
self.start = start
|
||||
self.duration = duration
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.key} ({self.start} - {self.duration})"
|
||||
|
||||
|
||||
def read_midi(file):
|
||||
notes = []
|
||||
notes_on = {}
|
||||
s = 0
|
||||
for msg in MidiFile(file):
|
||||
d = msg.dict()
|
||||
s += d['time'] * 1000
|
||||
if d["type"] == "note_on":
|
||||
notes_on[d["note"]] = s
|
||||
if d["type"] == "note_off":
|
||||
duration = s - notes_on[d["note"]]
|
||||
notes_on[d["note"]] = s
|
||||
notes.append(Key(d["note"], s, duration))
|
||||
return notes
|
||||
|
||||
|
||||
keys_to_play = read_midi(sys.argv[1])
|
||||
for i in map(lambda x: str(x), keys_to_play):
|
||||
print(str(i))
|
||||
|
||||
# List of keys currently holded. Format: (key, timestamp)
|
||||
keys_down = []
|
||||
|
||||
points = 0
|
||||
|
||||
|
||||
def print_device_info():
|
||||
pygame.midi.init()
|
||||
_print_device_info()
|
||||
pygame.midi.quit()
|
||||
|
||||
|
||||
def _print_device_info():
|
||||
for i in range(pygame.midi.get_count()):
|
||||
r = pygame.midi.get_device_info(i)
|
||||
(interf, name, input, output, opened) = r
|
||||
|
||||
in_out = ""
|
||||
if input:
|
||||
in_out = "(input)"
|
||||
if output:
|
||||
in_out = "(output)"
|
||||
|
||||
print(
|
||||
"%2i: interface :%s:, name :%s:, opened :%s: %s"
|
||||
% (i, interf, name, opened, in_out)
|
||||
)
|
||||
|
||||
def poll(midi):
|
||||
if midi.poll():
|
||||
[((status, key, intensity, data3), timestamp)] = midi.read(1)
|
||||
# For status, see STATUS DEFINITIONS up there (either TOUCH_DOWN, TOUCH_UP or others for pedals)
|
||||
# The key is between 21 and 108, C5 is 60
|
||||
# The itensity is how strong the key got struck (between 1 and 130ish)
|
||||
# data3 seems to always be 0
|
||||
# timestamp seems to be a unix timestamp since the midi has been oppened.
|
||||
|
||||
# Sometimes, status is always TOUCH_DOWN so if the key is already down, we consider it the same as a key up
|
||||
is_down = any(x[0] == key for x in keys_down)
|
||||
if status == TOUCH_DOWN and not is_down:
|
||||
keys_down.append((key, timestamp))
|
||||
# print(f"Midi: {status} - {key} - {intensity} - {data3} at {timestamp}")
|
||||
elif status == TOUCH_UP or is_down:
|
||||
down_since = next(since for (h_key, since) in keys_down if h_key == key)
|
||||
keys_down.remove((key, down_since))
|
||||
return Key(key, down_since, (timestamp - down_since))
|
||||
|
||||
def is_timing_close(key, i):
|
||||
return abs(i.start - key.start) < 500
|
||||
|
||||
def run(midi):
|
||||
global points
|
||||
clock_now = 0
|
||||
while sorted(keys_to_play, key=lambda x: x.start)[-1].start > clock_now:
|
||||
key = poll(midi)
|
||||
if key is None:
|
||||
continue
|
||||
clock_now = key.start
|
||||
|
||||
to_play = next((i for i in keys_to_play if i.key == key.key and is_timing_close(key, i)), None)
|
||||
if to_play == None:
|
||||
points -= 50
|
||||
print(f"Invalid key.")
|
||||
else:
|
||||
tempo_percent = abs((key.duration / to_play.duration) - 1)
|
||||
points += tempo_percent * 50
|
||||
if tempo_percent < .3 :
|
||||
print("Too short" if key.duration < to_play.duration else "Too long")
|
||||
elif tempo_percent < .5:
|
||||
print(f"GREAT.")
|
||||
else:
|
||||
print(f"EXCELLENT.")
|
||||
points -= len(keys_to_play) * 20
|
||||
|
||||
|
||||
def input_main(device_id=None):
|
||||
pg.init()
|
||||
pygame.midi.init()
|
||||
|
||||
_print_device_info()
|
||||
|
||||
if device_id is None:
|
||||
input_id = pygame.midi.get_default_input_id()
|
||||
else:
|
||||
input_id = device_id
|
||||
|
||||
print("using input_id :%s:" % input_id)
|
||||
i = pygame.midi.Input(input_id)
|
||||
|
||||
pg.display.set_mode((1, 1))
|
||||
try:
|
||||
run(i)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
print(f"You got: {int(points)}pts")
|
||||
pygame.midi.quit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(input_main(int(sys.argv[2]) if len(sys.argv) == 3 else None))
|
||||
Reference in New Issue
Block a user