Piezo music editor
This commit is contained in:
parent
3450695885
commit
2b84b73792
82
src/components/forms/EvoDropDown.tsx
Normal file
82
src/components/forms/EvoDropDown.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type EvoDropDownItem = {
|
||||||
|
name: string,
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EvoDropDownProps {
|
||||||
|
className?: string;
|
||||||
|
name?: string;
|
||||||
|
items: EvoDropDownItem[];
|
||||||
|
initItem?: EvoDropDownItem;
|
||||||
|
onValueChange?: (item: EvoDropDownItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuProps extends EvoDropDownProps {
|
||||||
|
appear: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Menu ({ className, items, initItem, appear, onValueChange }: MenuProps) {
|
||||||
|
const [currentItem, setItem] = useState<EvoDropDownItem>(initItem || items[0]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`absolute p-2 flex flex-col w-full bg-black border mt-3 border-border rounded-lg ${appear ? "block" : "hidden"} ${className}`}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<button
|
||||||
|
className="py-2 px-3 flex justify-between items-center bg-transparent bg-opacity-100 hover:bg-indigo-300 hover:bg-opacity-20 rounded-md"
|
||||||
|
onClick={() => {
|
||||||
|
setItem(item);
|
||||||
|
if (onValueChange) onValueChange(item)}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="text-left text-sm">
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
{currentItem.value == item.value && <div className="rounded-full bg-white w-2 h-2"></div>}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function EvoDropDown ({ className, name, items, initItem, onValueChange }: EvoDropDownProps) {
|
||||||
|
const [appear, setAppear] = useState<boolean>(false);
|
||||||
|
const [currentItem, setItem] = useState<EvoDropDownItem>(initItem || items[0]);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
setAppear(!appear);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<button className={`flex items-center justify-between cursor-pointer bg-finn border border-border rounded-lg ${className}`} onClick={handleClick}>
|
||||||
|
<div className="w-fit p-2 text-sm ps-4">{name}</div>
|
||||||
|
<div className="w-1/2 h-full p-2 text-right pe-4 text-sm font-roboto-mono bg-transparent">
|
||||||
|
{currentItem.name}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div className={`absolute p-2 flex flex-col w-full bg-black border mt-3 border-border rounded-lg ${appear ? "block" : "hidden"}`}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<button
|
||||||
|
className="py-2 px-3 flex justify-between items-center bg-transparent bg-opacity-100 hover:bg-indigo-300 hover:bg-opacity-20 rounded-md"
|
||||||
|
onClick={() => {
|
||||||
|
setAppear(false);
|
||||||
|
setItem(item);
|
||||||
|
if (onValueChange) onValueChange(item)}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="text-left text-sm">
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
{currentItem.value == item.value && <div className="rounded-full bg-white w-2 h-2"></div>}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
EvoDropDown.Menu = Menu;
|
||||||
|
|
||||||
|
export default EvoDropDown;
|
@ -27,7 +27,7 @@ export default function EvoInput ({
|
|||||||
<input
|
<input
|
||||||
className="w-1/2 h-full p-2 text-right pe-4 text-sm font-roboto-mono bg-transparent"
|
className="w-1/2 h-full p-2 text-right pe-4 text-sm font-roboto-mono bg-transparent"
|
||||||
type={type}
|
type={type}
|
||||||
name=""
|
name={name}
|
||||||
id=""
|
id=""
|
||||||
value={value}
|
value={value}
|
||||||
ref={labelRef}
|
ref={labelRef}
|
||||||
|
31
src/components/forms/Input.tsx
Normal file
31
src/components/forms/Input.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { ChangeEventHandler, HTMLInputTypeAttribute } from "react";
|
||||||
|
|
||||||
|
interface InputProps {
|
||||||
|
className?: string;
|
||||||
|
name?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
value?: string;
|
||||||
|
type?: HTMLInputTypeAttribute
|
||||||
|
onChange?: ChangeEventHandler<HTMLInputElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Input ({
|
||||||
|
className,
|
||||||
|
name,
|
||||||
|
placeholder,
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
onChange
|
||||||
|
}: InputProps) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={`px-4 py-2 text-left text-sm font-roboto-mono bg-finn border border-border rounded-lg ${className}`}
|
||||||
|
placeholder={placeholder}
|
||||||
|
type={type}
|
||||||
|
name={name}
|
||||||
|
id=""
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
236
src/components/landing/ControlPiezoMusicEditor.tsx
Normal file
236
src/components/landing/ControlPiezoMusicEditor.tsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import { ChangeEvent, MouseEventHandler, useState } from "react";
|
||||||
|
import { usePiezoMusic } from "../../hooks";
|
||||||
|
import { PiezoMusic } from "../../types/board";
|
||||||
|
import EvoInput from "../forms/EvoInput";
|
||||||
|
import Input from "../forms/Input";
|
||||||
|
import EvoDropDown from "../forms/EvoDropDown";
|
||||||
|
import Button from "../forms/Button";
|
||||||
|
import { pitch } from "../../data/melodies";
|
||||||
|
import { PatchPiezoMusic } from "../../controllers/BoardController";
|
||||||
|
|
||||||
|
function getNoteItems () {
|
||||||
|
const pitches = Object.keys(pitch);
|
||||||
|
const notes = pitches.map(n => {
|
||||||
|
const note = n.replace("S", "#");
|
||||||
|
return {name: note, value: note}
|
||||||
|
})
|
||||||
|
const additional = [
|
||||||
|
{
|
||||||
|
name: ". (Jump note)",
|
||||||
|
value: "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "- (Hold note)",
|
||||||
|
value: "-"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return [...additional, ...notes];
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoteItem ({note, index, parentIndex}: { note: string, index: number, parentIndex: number }) {
|
||||||
|
const { changeNote } = usePiezoMusic();
|
||||||
|
|
||||||
|
const [dropAppear, setApper] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const noteItems = getNoteItems();
|
||||||
|
const toggleDropdown = () => { setApper(!dropAppear) };
|
||||||
|
const handleChange = (item: { name: string, value: any }) => {
|
||||||
|
changeNote(parentIndex, index, item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className="min-w-40 h-40 transition border border-border rounded-lg hover:bg-finn animate-size-fade-in" onClick={toggleDropdown}>
|
||||||
|
<div className="text-xl">
|
||||||
|
{note}
|
||||||
|
</div>
|
||||||
|
<EvoDropDown.Menu
|
||||||
|
className="!w-32 top-1 h-56 z-30 overflow-y-scroll v-scrollbar"
|
||||||
|
appear={dropAppear}
|
||||||
|
items={noteItems}
|
||||||
|
onValueChange={handleChange}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NotePlus ({ parentIndex }: { parentIndex: number }) {
|
||||||
|
const { addNote } = usePiezoMusic();
|
||||||
|
const [dropAppear, setApper] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const noteItems = getNoteItems();
|
||||||
|
const toggleDropdown = () => { setApper(!dropAppear) };
|
||||||
|
|
||||||
|
const handleAddNote = (item: { name: string, value: any }) => {
|
||||||
|
addNote(parentIndex, item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className="border border-border bg-secondary-solid rounded-lg min-w-40 h-40" onClick={toggleDropdown}>
|
||||||
|
<i className="bi bi-plus text-2xl"></i>
|
||||||
|
<EvoDropDown.Menu
|
||||||
|
className="!w-32 top-1 h-56 z-30 overflow-y-scroll v-scrollbar"
|
||||||
|
appear={dropAppear}
|
||||||
|
items={noteItems}
|
||||||
|
onValueChange={handleAddNote}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PiezoEditor ({ piezo, index }: { piezo: PiezoMusic, index: number }) {
|
||||||
|
const { setName, setPin, setBeat, setTempo } = usePiezoMusic();
|
||||||
|
|
||||||
|
const beatsItem = [
|
||||||
|
{
|
||||||
|
name: "1/2",
|
||||||
|
value: 1/2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1/4",
|
||||||
|
value: 1/4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1/8",
|
||||||
|
value: 1/8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1/16",
|
||||||
|
value: 1/16
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const initBeatItem = beatsItem.find((item) => item.value == piezo.beats);
|
||||||
|
|
||||||
|
const handle = {
|
||||||
|
changeName: (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||||
|
setName(index, e.target.value);
|
||||||
|
},
|
||||||
|
pinChange: (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||||
|
setPin(index, e.target.value);
|
||||||
|
},
|
||||||
|
beatChange: (item: { name: string, value: any }) => {
|
||||||
|
setBeat(index, item.value);
|
||||||
|
},
|
||||||
|
tempoChange: (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||||
|
const tempo = Number.parseInt(e.target.value);
|
||||||
|
setTempo(index, tempo);
|
||||||
|
},
|
||||||
|
play: () => {
|
||||||
|
PatchPiezoMusic(piezo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-5 bg-secondary border border-border rounded-lg p-5 animate-size-fade-in">
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<div className="flex flex-row gap-3">
|
||||||
|
<Input
|
||||||
|
className="w-64"
|
||||||
|
placeholder="Enter name"
|
||||||
|
value={piezo.name}
|
||||||
|
onChange={handle.changeName}
|
||||||
|
/>
|
||||||
|
<EvoInput
|
||||||
|
className="w-36"
|
||||||
|
name="Pin"
|
||||||
|
type="number"
|
||||||
|
value={piezo.pin.toString()}
|
||||||
|
onChange={handle.pinChange}
|
||||||
|
/>
|
||||||
|
<EvoDropDown
|
||||||
|
className="w-36"
|
||||||
|
name="Beats"
|
||||||
|
items={beatsItem}
|
||||||
|
initItem={initBeatItem}
|
||||||
|
/>
|
||||||
|
<EvoInput
|
||||||
|
className="w-36"
|
||||||
|
name="Tempo"
|
||||||
|
type="number"
|
||||||
|
value={piezo.tempo.toString()}
|
||||||
|
onChange={handle.tempoChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3">
|
||||||
|
<Button.Secondary className="!py-0 !px-6 flex items-center gap-3">
|
||||||
|
<div className="text-sm">Export</div>
|
||||||
|
<i className="bi bi-upload text-sm"></i>
|
||||||
|
</Button.Secondary>
|
||||||
|
<Button.Primary className="!py-1 !px-8" onClick={handle.play}>
|
||||||
|
<i className="bi bi-play-fill"></i>
|
||||||
|
</Button.Primary>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="v-scrollbar flex flex-row flex-nowrap gap-4 overflow-x-scroll overflow-y-hidden relative">
|
||||||
|
{piezo.notes.map((note, i) => (
|
||||||
|
<NoteItem
|
||||||
|
note={note}
|
||||||
|
index={i}
|
||||||
|
parentIndex={index}
|
||||||
|
key={i}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<NotePlus parentIndex={index} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BarPlus ({ onClick }: { onClick?: MouseEventHandler<HTMLButtonElement> }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="bg-finn h-36 hover:bg-secondary transition col-span-2 rounded-lg border border-border flex items-center justify-center"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<i className="bi bi-plus text-6xl text-border"></i>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ControlPiezoMusicEditor () {
|
||||||
|
const { piezeNotes, addPiezo } = usePiezoMusic();
|
||||||
|
|
||||||
|
const handleAdd = (): void => {
|
||||||
|
let anopin = 13;
|
||||||
|
for (let i = 0; i < piezeNotes.length; i++) {
|
||||||
|
if (piezeNotes.filter(piezo => piezo.pin == anopin).length > 0) {
|
||||||
|
anopin--;
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
addPiezo({
|
||||||
|
name: "test",
|
||||||
|
pin: anopin,
|
||||||
|
notes: [],
|
||||||
|
beats: 1/4,
|
||||||
|
tempo: 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container grid items-center relative">
|
||||||
|
<div className={`col-span-8 w-[inherit]`}>
|
||||||
|
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
|
||||||
|
Piezo Music Editor
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-8 mb-8">
|
||||||
|
<p className="col-span-6">Lorem, ipsum dolor sit amet consectetur adipisicing elit. Omnis rem, perspiciatis voluptatibus dolor officiis voluptas asperiores reiciendis quisquam qui numquam quas illum velit in id, est expedita ipsa voluptatum eligendi?</p>
|
||||||
|
</div>
|
||||||
|
<div className={`flex flex-col gap-6`}>
|
||||||
|
{piezeNotes.map((piezo, i) => (
|
||||||
|
<PiezoEditor
|
||||||
|
piezo={piezo}
|
||||||
|
index={i}
|
||||||
|
key={i}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<BarPlus onClick={handleAdd} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ControlPiezoMusicEditor;
|
@ -1,17 +1,23 @@
|
|||||||
import { Dispatch, ReactNode, SetStateAction, createContext, useState } from "react";
|
import { Dispatch, ReactNode, SetStateAction, createContext, useState } from "react";
|
||||||
import { ChannelPinState, DynamicPinState, PinMode, PinState } from "../types/board";
|
import { ChannelPinState, DynamicPinState, PiezoMusic, PinMode, PinState } from "../types/board";
|
||||||
|
|
||||||
|
import ExampleMusic1 from "../data/music/ode-to-joy.json";
|
||||||
|
import ExampleMusic2 from "../data/music/fallen-down.json";
|
||||||
|
|
||||||
interface ControllerContextProps {
|
interface ControllerContextProps {
|
||||||
pinModes: PinMode[];
|
pinModes: PinMode[];
|
||||||
leds: PinState[];
|
leds: PinState[];
|
||||||
rgbLed: ChannelPinState[];
|
rgbLed: ChannelPinState[];
|
||||||
piezo: DynamicPinState[];
|
piezo: DynamicPinState[];
|
||||||
|
piezeNotes: PiezoMusic[];
|
||||||
motoServo: DynamicPinState[];
|
motoServo: DynamicPinState[];
|
||||||
photoresistor: DynamicPinState[];
|
photoresistor: DynamicPinState[];
|
||||||
|
|
||||||
setPinModes?: Dispatch<SetStateAction<PinMode[]>>;
|
setPinModes?: Dispatch<SetStateAction<PinMode[]>>;
|
||||||
setLeds?: Dispatch<SetStateAction<PinState[]>>;
|
setLeds?: Dispatch<SetStateAction<PinState[]>>;
|
||||||
setRgbLed?: Dispatch<SetStateAction<ChannelPinState[]>>;
|
setRgbLed?: Dispatch<SetStateAction<ChannelPinState[]>>;
|
||||||
setPiezo?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
setPiezo?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
||||||
|
setNotes?: Dispatch<SetStateAction<PiezoMusic[]>>;
|
||||||
setMotoServo?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
setMotoServo?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
||||||
setPhotoresistor?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
setPhotoresistor?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
||||||
}
|
}
|
||||||
@ -46,6 +52,10 @@ const INIT_VALUES: ControllerContextProps = {
|
|||||||
state: 262
|
state: 262
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
piezeNotes: [
|
||||||
|
ExampleMusic1,
|
||||||
|
ExampleMusic2
|
||||||
|
],
|
||||||
motoServo: [
|
motoServo: [
|
||||||
{
|
{
|
||||||
pin: "9",
|
pin: "9",
|
||||||
@ -67,6 +77,7 @@ export function BoardControllerProvider ({ children }: { children: ReactNode })
|
|||||||
const [leds, setLeds] = useState<PinState[]>(INIT_VALUES.leds);
|
const [leds, setLeds] = useState<PinState[]>(INIT_VALUES.leds);
|
||||||
const [rgbLed, setRgbLed] = useState<ChannelPinState[]>(INIT_VALUES.rgbLed);
|
const [rgbLed, setRgbLed] = useState<ChannelPinState[]>(INIT_VALUES.rgbLed);
|
||||||
const [piezo, setPiezo] = useState<DynamicPinState[]>(INIT_VALUES.piezo);
|
const [piezo, setPiezo] = useState<DynamicPinState[]>(INIT_VALUES.piezo);
|
||||||
|
const [piezeNotes, setNotes] = useState<PiezoMusic[]>(INIT_VALUES.piezeNotes);
|
||||||
const [motoServo, setMotoServo] = useState<DynamicPinState[]>(INIT_VALUES.motoServo);
|
const [motoServo, setMotoServo] = useState<DynamicPinState[]>(INIT_VALUES.motoServo);
|
||||||
const [photoresistor, setPhotoresistor] = useState<DynamicPinState[]>(INIT_VALUES.photoresistor);
|
const [photoresistor, setPhotoresistor] = useState<DynamicPinState[]>(INIT_VALUES.photoresistor);
|
||||||
|
|
||||||
@ -75,6 +86,7 @@ export function BoardControllerProvider ({ children }: { children: ReactNode })
|
|||||||
leds, setLeds,
|
leds, setLeds,
|
||||||
rgbLed, setRgbLed,
|
rgbLed, setRgbLed,
|
||||||
piezo, setPiezo,
|
piezo, setPiezo,
|
||||||
|
piezeNotes, setNotes,
|
||||||
motoServo, setMotoServo,
|
motoServo, setMotoServo,
|
||||||
photoresistor, setPhotoresistor
|
photoresistor, setPhotoresistor
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ChannelPinState } from "../types/board";
|
import { ChannelPinState, PiezoMusic } from "../types/board";
|
||||||
import { io } from "../socket/socket.io";
|
import { io } from "../socket/socket.io";
|
||||||
|
|
||||||
const url = "http://localhost:3000/api-arduino";
|
const url = "http://localhost:3000/api-arduino";
|
||||||
@ -46,6 +46,18 @@ export async function PatchPiezo (pin: number, freq: number) {
|
|||||||
console.log("Piezo Response: ", data);
|
console.log("Piezo Response: ", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function PatchPiezoMusic (music: PiezoMusic) {
|
||||||
|
const res = await axios.patch(`${url}/piezo/music`, music, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = res.data;
|
||||||
|
|
||||||
|
console.log("Piezo Response: ", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function PatchServo(pin: string | number, value: number) {
|
export async function PatchServo(pin: string | number, value: number) {
|
||||||
io.emit("servo", pin, value);
|
io.emit("servo", pin, value);
|
||||||
}
|
}
|
@ -10,13 +10,23 @@
|
|||||||
}
|
}
|
||||||
input::-webkit-outer-spin-button,
|
input::-webkit-outer-spin-button,
|
||||||
input::-webkit-inner-spin-button {
|
input::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Firefox */
|
/* Firefox */
|
||||||
input[type=number] {
|
input[type=number] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 3px;
|
||||||
|
}
|
||||||
|
.v-scrollbar::-webkit-scrollbar-track {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
.v-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-primary cursor-pointer rounded-full;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
91
src/data/melodies.ts
Normal file
91
src/data/melodies.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
export const pitch = {
|
||||||
|
B0: 31,
|
||||||
|
C1: 33,
|
||||||
|
CS1: 35,
|
||||||
|
D1: 37,
|
||||||
|
DS1: 39,
|
||||||
|
E1: 41,
|
||||||
|
F1: 44,
|
||||||
|
FS1: 46,
|
||||||
|
G1: 49,
|
||||||
|
GS1: 52,
|
||||||
|
A1: 55,
|
||||||
|
AS1: 58,
|
||||||
|
B1: 62,
|
||||||
|
C2: 65,
|
||||||
|
CS2: 69,
|
||||||
|
D2: 73,
|
||||||
|
DS2: 78,
|
||||||
|
E2: 82,
|
||||||
|
F2: 87,
|
||||||
|
FS2: 93,
|
||||||
|
G2: 98,
|
||||||
|
GS2: 104,
|
||||||
|
A2: 110,
|
||||||
|
AS2: 117,
|
||||||
|
B2: 123,
|
||||||
|
C3: 131,
|
||||||
|
CS3: 139,
|
||||||
|
D3: 147,
|
||||||
|
DS3: 156,
|
||||||
|
E3: 165,
|
||||||
|
F3: 175,
|
||||||
|
FS3: 185,
|
||||||
|
G3: 196,
|
||||||
|
GS3: 208,
|
||||||
|
A3: 220,
|
||||||
|
AS3: 233,
|
||||||
|
B3: 247,
|
||||||
|
C4: 262,
|
||||||
|
CS4: 277,
|
||||||
|
D4: 294,
|
||||||
|
DS4: 311,
|
||||||
|
E4: 330,
|
||||||
|
F4: 349,
|
||||||
|
FS4: 370,
|
||||||
|
G4: 392,
|
||||||
|
GS4: 415,
|
||||||
|
A4: 440,
|
||||||
|
AS4: 466,
|
||||||
|
B4: 494,
|
||||||
|
C5: 523,
|
||||||
|
CS5: 554,
|
||||||
|
D5: 587,
|
||||||
|
DS5: 622,
|
||||||
|
E5: 659,
|
||||||
|
F5: 698,
|
||||||
|
FS5: 740,
|
||||||
|
G5: 784,
|
||||||
|
GS5: 831,
|
||||||
|
A5: 880,
|
||||||
|
AS5: 932,
|
||||||
|
B5: 988,
|
||||||
|
C6: 1047,
|
||||||
|
CS6: 1109,
|
||||||
|
D6: 1175,
|
||||||
|
DS6: 1245,
|
||||||
|
E6: 1319,
|
||||||
|
F6: 1397,
|
||||||
|
FS6: 1480,
|
||||||
|
G6: 1568,
|
||||||
|
GS6: 1661,
|
||||||
|
A6: 1760,
|
||||||
|
AS6: 1865,
|
||||||
|
B6: 1976,
|
||||||
|
C7: 2093,
|
||||||
|
CS7: 2217,
|
||||||
|
D7: 2349,
|
||||||
|
DS7: 2489,
|
||||||
|
E7: 2637,
|
||||||
|
F7: 2794,
|
||||||
|
FS7: 2960,
|
||||||
|
G7: 3136,
|
||||||
|
GS7: 3322,
|
||||||
|
A7: 3520,
|
||||||
|
AS7: 3729,
|
||||||
|
B7: 3951,
|
||||||
|
C8: 4186,
|
||||||
|
CS8: 4435,
|
||||||
|
D8: 4699,
|
||||||
|
DS8: 4978
|
||||||
|
}
|
12
src/data/music/fallen-down.json
Normal file
12
src/data/music/fallen-down.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "Fallen Down",
|
||||||
|
"pin": 11,
|
||||||
|
"notes": [
|
||||||
|
"F#5", "C#5", "F#5", "C#5", "F#5", "C#5", "F#5", "C#5", "F#5", "C#5", "F#5", "C#5",
|
||||||
|
"B4", "A4", "C#5", ".", "A4", "B4", "E5", "D#5", "E5", "F#5", "D#5", "B4",
|
||||||
|
"F#5", "B4", "F#5", "B4", "F#5", "B4", "F#5", "A#4", "F#5", "A#4", "G5", ".",
|
||||||
|
"F#5", "D5", "F#5", "D5", "E5", "F#5", "E5", "-", "D5", "-", "C#5", "-"
|
||||||
|
],
|
||||||
|
"beats": 0.5,
|
||||||
|
"tempo": 100
|
||||||
|
}
|
9
src/data/music/ode-to-joy.json
Normal file
9
src/data/music/ode-to-joy.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "Ode to joy",
|
||||||
|
"pin": 11,
|
||||||
|
"notes": [
|
||||||
|
"E4", "E4", "F4", "G4", "G4", "F4", "E4", "D4", "C4", "C4", "D4", "E4", "D4", "-", "C4", "C4"
|
||||||
|
],
|
||||||
|
"beats": 0.5,
|
||||||
|
"tempo": 100
|
||||||
|
}
|
@ -1,6 +1,11 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { BoardControllerContext } from "../contexts/BoardController";
|
import { BoardControllerContext } from "../contexts/BoardController";
|
||||||
import { ChannelPinState } from "../types/board";
|
import { ChannelPinState, PiezoMusic } from "../types/board";
|
||||||
|
|
||||||
|
|
||||||
|
// * -----------------------------------
|
||||||
|
// * I'm never use OOP in react lol
|
||||||
|
// * -----------------------------------
|
||||||
|
|
||||||
export function usePin () {
|
export function usePin () {
|
||||||
return useContext(BoardControllerContext);
|
return useContext(BoardControllerContext);
|
||||||
@ -119,6 +124,105 @@ export function usePiezo () {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function usePiezoMusic () {
|
||||||
|
const { piezeNotes, setNotes } = useContext(BoardControllerContext);
|
||||||
|
|
||||||
|
const getPiezo = (pin: number | string) => {
|
||||||
|
return piezeNotes.find(val => val.pin == pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addPiezo = (notes: PiezoMusic) => {
|
||||||
|
const newPiezo = [...piezeNotes, notes];
|
||||||
|
setNotes!(newPiezo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPiezo = (index: number, piezo: PiezoMusic) => {
|
||||||
|
setNotes!(piezos => piezos.map((_piezo, i) => {
|
||||||
|
if (i == index) return piezo;
|
||||||
|
return _piezo;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const removePiezo = (index: number) => {
|
||||||
|
const newPiezo = piezeNotes.filter((_piezo, i) => i != index);
|
||||||
|
setNotes!(newPiezo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNote = (piezoIndex: number, note: string) => {
|
||||||
|
setNotes!(piezos => piezos.map((piezo, i) => {
|
||||||
|
if (piezoIndex == i) {
|
||||||
|
piezo.notes.push(note);
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeNote = (piezoIndex: number, noteIndex: number, note: string) => {
|
||||||
|
setNotes!(piezos => piezos.map((piezo, i) => {
|
||||||
|
if (piezoIndex == i) {
|
||||||
|
piezo.notes[noteIndex] = note;
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeNote = (piezoIndex: number, noteIndex: number) => {
|
||||||
|
setNotes!(piezos => piezos.map((piezo, i) => {
|
||||||
|
if (piezoIndex == i) {
|
||||||
|
piezo.notes = piezo.notes.filter((_p, i) => i != noteIndex);
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPin = (index: number, newPin: string | number) => {
|
||||||
|
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||||
|
if (i == index) {
|
||||||
|
piezo.pin = newPin;
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
})
|
||||||
|
setNotes!(newPiezo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setName = (index: number, name: string) => {
|
||||||
|
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||||
|
if (i == index) {
|
||||||
|
piezo.name = name;
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
})
|
||||||
|
setNotes!(newPiezo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setBeat = (index: number, beats: number) => {
|
||||||
|
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||||
|
if (i == index) {
|
||||||
|
piezo.beats = beats;
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
})
|
||||||
|
setNotes!(newPiezo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTempo = (index: number, tempo: number) => {
|
||||||
|
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||||
|
if (i == index) {
|
||||||
|
piezo.tempo = tempo;
|
||||||
|
}
|
||||||
|
return piezo;
|
||||||
|
})
|
||||||
|
setNotes!(newPiezo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
piezeNotes, setNotes, getPiezo, addPiezo, removePiezo, setPiezo,
|
||||||
|
setPin, setName, setBeat, setTempo,
|
||||||
|
addNote, changeNote, removeNote
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useServo () {
|
export function useServo () {
|
||||||
const { motoServo, setMotoServo } = useContext(BoardControllerContext);
|
const { motoServo, setMotoServo } = useContext(BoardControllerContext);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import ControlPhotoresistor from "../components/landing/ControlPhotoresistor";
|
|||||||
import ControlServo from "../components/landing/ControlServo";
|
import ControlServo from "../components/landing/ControlServo";
|
||||||
import Closing from "../components/landing/Closing";
|
import Closing from "../components/landing/Closing";
|
||||||
import Footer from "../components/Footer";
|
import Footer from "../components/Footer";
|
||||||
|
import ControlPiezoMusicEditor from "../components/landing/ControlPiezoMusicEditor";
|
||||||
|
|
||||||
function MainPage () {
|
function MainPage () {
|
||||||
const led = useRef<HTMLDivElement | null>(null);
|
const led = useRef<HTMLDivElement | null>(null);
|
||||||
@ -32,6 +33,7 @@ function MainPage () {
|
|||||||
<ControlLED refto={led} />
|
<ControlLED refto={led} />
|
||||||
<ControlRgbLed refto={rgbLed} />
|
<ControlRgbLed refto={rgbLed} />
|
||||||
<ControlPiezo refto={piezo} />
|
<ControlPiezo refto={piezo} />
|
||||||
|
<ControlPiezoMusicEditor />
|
||||||
<ControlServo refto={servo} />
|
<ControlServo refto={servo} />
|
||||||
<ControlPhotoresistor refto={photoresistor} />
|
<ControlPhotoresistor refto={photoresistor} />
|
||||||
<Closing />
|
<Closing />
|
||||||
|
@ -18,3 +18,11 @@ export interface ChannelPinState {
|
|||||||
green: PinState,
|
green: PinState,
|
||||||
blue: PinState
|
blue: PinState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PiezoMusic {
|
||||||
|
name: string,
|
||||||
|
pin: number | string;
|
||||||
|
notes: string[];
|
||||||
|
beats: number;
|
||||||
|
tempo: number;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user