Skip to content

Commit

Permalink
Add chord editing
Browse files Browse the repository at this point in the history
  • Loading branch information
ArijanJ committed Aug 24, 2024
1 parent ca48818 commit 0bd0383
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 5 deletions.
27 changes: 23 additions & 4 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import SheetActions from "./components/SheetActions.svelte";
import Guide from "./components/Guide.svelte";
import ChordEditor from "./components/ChordEditor.svelte";
let existingProject = {
element: undefined,
Expand Down Expand Up @@ -144,8 +145,6 @@
await fileInput.files[0].arrayBuffer().then((arrbuf) =>{
MIDIObject = getMIDIFileFromArrayBuffer(arrbuf)
let tempo = getTempo(MIDIObject).ticksPerBeat
if(!getTempo(MIDIObject).ticksPerBeat)
console.error("No ticksPerBeat in this midi file")
Expand Down Expand Up @@ -179,7 +178,7 @@
}
/**
* Recreate the chord with existing data (for e.g. reordering purposes)
* Recreate all chords with existing data (e.g. for reordering purposes)
*
* Calls updateChords()
*/
Expand Down Expand Up @@ -224,7 +223,6 @@
if (["beats",
"breaks",
"classicChordOrder",
"quantize",
"sequentialQuantize",
"minSpeedChange",
Expand Down Expand Up @@ -650,6 +648,21 @@
transposeRegion(selection.left, selection.right, transposition)
}
let editChordDialog = undefined
let chordToEdit = undefined
let editChord = () => {
let chord = chord_at(selection.left)
if (not_chord(chord)) return
chordToEdit = chord
editChordDialog.showModal()
}
let chordChanged = (e) => {
chords_and_otherwise[real_index_of(chordToEdit.index)].notes = e.detail.notes
updateChords()
}
</script>
<svelte:window on:keydown={(e) => {
Expand All @@ -663,6 +676,10 @@
}
}}></svelte:window>
<!-- Only shown if needed -->
<ChordEditor chord={chordToEdit} {settings} bind:dialog={editChordDialog}
on:chordChanged={chordChanged}/>
<svelte:head>
<title>MIDI Converter</title>
</svelte:head>
Expand Down Expand Up @@ -774,6 +791,8 @@ Individual sizes are an estimation, the total is correct.">ⓘ</span>
on:click={() => { chord_at(selection.left-1).reflow = true; saveSheet() }}>Make measure beginning</button>
<button class="w-full block" on:click={() => { addComment(selection.left) }}>Add a comment</button>
</div>
<button on:click={() => { editChord() }}>Edit chord</button>
{/if}
</div>
<SheetOptions
Expand Down
100 changes: 100 additions & 0 deletions src/components/ChordEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<script>
import { createEventDispatcher } from "svelte"
import { render_chord } from "../utils/Rendering"
import { Note, Chord } from "../utils/VP"
import Keyboard from "./Piano/Keyboard.svelte"
let dispatch = createEventDispatcher()
let firstNote = undefined
// Stores the chord while it's being edited (not saved yet in case the user wants to cancel)
let tempBuffer = undefined
export let chord = undefined
$: {
if (chord?.notes?.length > 0) {
firstNote = chord.notes[0]
tempBuffer = JSON.parse(JSON.stringify(chord))
}
}
export let settings = undefined
export let dialog = undefined
let noteToAdd = undefined
let removeNote = (i) => {
if(i.detail) {
// This is now a MIDI note value passed from the Keyboard
i = tempBuffer.notes.findIndex(note => note.original === i.detail)
}
tempBuffer.notes.splice(i, 1)
tempBuffer = new Chord(tempBuffer) // regen for correct sorting
}
let addNote = (event) => {
let clone = JSON.parse(JSON.stringify(firstNote))
let transposition = clone.value - clone.original
clone.original = event.detail
clone.value = clone.original + transposition
tempBuffer.notes.push(new Note(clone))
tempBuffer = new Chord(tempBuffer) // regen for correct sorting
}
let applyChanges = () => {
let notes = tempBuffer.notes.map(n => new Note(n))
let newChord = new Chord(notes)
dispatch('chordChanged', newChord)
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<dialog bind:this={dialog} on:click|self={() => {dialog.close()}} class="rounded-lg overflow-hidden p-2">
<div class="flex flex-col items-center gap-2">
<div id="chord" style="background-color: #2D2A32" class="text-2xl p-1 rounded-md">
{@html render_chord(tempBuffer, undefined, settings, false)}
</div>
{#if tempBuffer}
<div class="flex flex-col items-center justify-center gap-2">
<p>Remove a note:</p>
<div class="flex flex-row">
{#each tempBuffer.notes as note, i}
<div class="w-full">
<button on:click={() => { removeNote(i) }}
class="text-2xl text-nowrap border-none">
{note.char ?? '[invalid]'}
</button>
</div>
{/each}
</div>
<p>Toggle a note:</p>
<Keyboard
octaves=7
keysPressed={tempBuffer.notes.map(note => note.original)}
on:noteon={addNote}
on:noteoff={removeNote}/>
<!-- Don't really need this I suppose? -->
<!-- <div id="buttons">
<input type="text" class="w-12" bind:value={noteToAdd}>
<button>Add from QWERTY (at {firstNote.transposition} transposition)</button>
</div> -->
<hr class="my-2 mx-1 border-black border-1 w-full">
<button on:click={() => { dialog.close(); applyChanges() }}>Apply</button>
</div>
{/if}
</div>
</dialog>
<style>
* {
color: black
}
</style>
2 changes: 2 additions & 0 deletions src/components/Guide.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Double-click on a note to select the whole line<br>
Middle-click to select everything below the note<br>
<hr class="my-2 mx-1">
Note that <span title="Break every x beats, quantize, and any tempo/BPM changes" class="underline cursor-pointer">some settings</span> must fully regenerate your sheet, so you may lose some manual changes.<br>
<hr class="my-2 mx-1">
Timing:<br>
<span style="color: {colors.quadruple}">Longer than whole note</span><br>
<span style="color: {colors.whole}">Whole note (s.... f)</span><br>
Expand Down
84 changes: 84 additions & 0 deletions src/components/Piano/Key.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script>
// Credits to https://github.com/danferns/svelte-piano
export let noteNum;
export let keyWidth = 8;
export let pressed = false;
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let isNatural = ![1, 3, 6, 8, 10].includes(noteNum % 12);
let bias = 0;
// the accidental keys are not perfectly in center
if (!isNatural) {
if ([1, 6].includes(noteNum % 12)) bias = -keyWidth / 12;
else if ([3, 10].includes(noteNum % 12)) bias = keyWidth / 12;
}
function keyPressed() {
if (pressed) {
dispatch("noteoff", noteNum)
pressed = false
return
}
dispatch("noteon", noteNum);
pressed = true;
}
function keyReleased() {
return
if (!pressed) return;
dispatch("noteoff", noteNum);
pressed = false;
}
</script>

<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class:accidental={!isNatural}
class:natural={isNatural}
class:pressed
style="--width: {keyWidth - keyWidth * 0.47 * !isNatural}px; transform: translate({bias}px);"
draggable="false"
on:mousedown|preventDefault={keyPressed}
on:mouseup|preventDefault={keyReleased}
on:mouseenter={(e) => { if (e.buttons) keyPressed(); }}
on:mouseleave={(e) => { if (e.buttons) keyReleased(); }}
on:touchstart|preventDefault={keyPressed}
on:touchend|preventDefault={keyReleased}
/>

<style>
div {
flex-shrink: 0;
width: var(--width);
min-width: min-content;
border-radius: 0px 0px calc(var(--width) / 8) calc(var(--width) / 8);
-webkit-user-drag: none;
}
.accidental {
margin: 0px calc(var(--width) / -2) 0px calc(var(--width) / -2);
z-index: 2;
height: 60%;
background: black;
box-shadow: inset white 0px 0px 2px 0px;
}
.natural {
height: 100%;
box-shadow: inset black 0px 0px 2px 0px;
}
.accidental.pressed {
background: #ff966d;
}
.natural.pressed {
background: #ff966d;
}
</style>
35 changes: 35 additions & 0 deletions src/components/Piano/Keyboard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script>
// Credits to https://github.com/danferns/svelte-piano
export let octaves = 2;
export let middleC = 60;
export let keysPressed = [];
import Key from "./Key.svelte";
let keys;
$: keys = [...Array(octaves * 12 + 1).keys()].map(
(i) => i + (middleC - Math.floor(octaves / 2) * 12)
);
</script>

<div class="keyboard">
<div>
{#each keys as note}
<Key noteNum={note} on:noteon on:noteoff pressed={keysPressed.includes(note)}/>
{/each}
</div>
</div>

<style>
.keyboard {
display: flex;
justify-content: center;
}
.keyboard > div {
display: flex;
overflow: auto;
padding: 8px;
height: 48px;
}
</style>
5 changes: 4 additions & 1 deletion src/utils/VP.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const lastPossibleNote = 108

const capitalNotes = "!@#$%^&*()QWERTYUIOPASDFGHJKLZXCBVNM"

let is_chord = (x) => { try { return 'notes' in x } catch { return false } }
let is_chord = (x) => { try { return 'notes' in x && x.notes.length != 0 } catch { return false } }
let not_chord = (x) => { try { return !is_chord(x) } catch { return true } } // !x || (x.type && (x.type == "break" || x.type == "comment"))
class Note {
constructor(value, playTime, ticks, tempo, BPM, delta, shifts='keep', oors='keep', skipOrdering=false) {
Expand Down Expand Up @@ -63,6 +63,9 @@ class Note {

class Chord {
constructor(notes, classicChordOrder = true, sequentialQuantize = true, skipProcessing = false) {
if('notes' in notes) // It's actually another chord - copy constructor
var { notes, classicChordOrder, sequentialQuantize } = notes

let is_quantized = false
let previous_note = notes[0]

Expand Down

0 comments on commit 0bd0383

Please sign in to comment.