diff --git a/localtypings/pxtmusic.d.ts b/localtypings/pxtmusic.d.ts index 7961550e4f26..6cf3ea0bd36b 100644 --- a/localtypings/pxtmusic.d.ts +++ b/localtypings/pxtmusic.d.ts @@ -60,6 +60,7 @@ declare namespace pxt.assets.music { } export interface DrumInstrument { + name?: string; startFrequency: number; startVolume: number; steps: DrumSoundStep[]; diff --git a/pxtblocks/fields/fieldEditorRegistry.ts b/pxtblocks/fields/fieldEditorRegistry.ts index ce463a52bfe2..5a2b76fbf4cd 100644 --- a/pxtblocks/fields/fieldEditorRegistry.ts +++ b/pxtblocks/fields/fieldEditorRegistry.ts @@ -31,6 +31,7 @@ import { FieldSoundEffect } from "./field_sound_effect"; import { FieldAutoComplete } from "./field_autocomplete"; import { FieldColorWheel } from "./field_colorwheel"; import { FieldScopedValueSelector } from "./field_scopedvalueselector"; +import { FieldPianoRoll } from "./field_piano_roll"; interface FieldEditorOptions { field: FieldCustomConstructor; @@ -70,6 +71,7 @@ export function initFieldEditors() { registerFieldEditor('scopedvalueselector', FieldScopedValueSelector); if (pxt.appTarget.appTheme?.songEditor) { registerFieldEditor('musiceditor', FieldMusicEditor); + registerFieldEditor('pianoroll', FieldPianoRoll); } } diff --git a/pxtblocks/fields/field_asset.ts b/pxtblocks/fields/field_asset.ts index e4f07e51b85e..38ece713c996 100644 --- a/pxtblocks/fields/field_asset.ts +++ b/pxtblocks/fields/field_asset.ts @@ -96,21 +96,18 @@ export abstract class FieldAssetEditor { const existing = song.tracks.find(t => t.id === index); - if (existing) track.notes = existing.notes; + if (existing) { + track.notes = existing.notes; + if (track.instrument) { + track.instrument.octave = existing.instrument?.octave || track.instrument.octave; + } + } return track; }) } @@ -709,7 +714,7 @@ namespace pxt.assets.music { }, drums: [ { - /* neutral kick */ + name: lf("neutral kick"), startFrequency: 100, startVolume: 1024, steps: [ @@ -728,7 +733,7 @@ namespace pxt.assets.music { ] }, { - /* punchy kick */ + name: lf("punchy kick"), startFrequency: 200, startVolume: 1024, steps: [{ @@ -740,7 +745,7 @@ namespace pxt.assets.music { }, { - /* booming kick */ + name: lf("booming kick"), startFrequency: 100, startVolume: 1024, steps: [{ @@ -753,7 +758,7 @@ namespace pxt.assets.music { { - /* snare 1 */ + name: lf("snare 1"), startFrequency: 175, startVolume: 1024, steps: [ @@ -785,7 +790,7 @@ namespace pxt.assets.music { }, { - /* snare 2 */ + name: lf("snare 2"), startFrequency: 220, startVolume: 1024, steps: [ @@ -818,7 +823,7 @@ namespace pxt.assets.music { { - /* hat 1 */ + name: lf("hat 1"), startFrequency: 400, startVolume: 500, steps: [ @@ -838,7 +843,7 @@ namespace pxt.assets.music { }, { - /* hat 2 */ + name: lf("hat 2"), startFrequency: 400, startVolume: 0, steps: [ @@ -865,7 +870,7 @@ namespace pxt.assets.music { { - /* hat 3 */ + name: lf("hat 3"), startFrequency: 400, startVolume: 0, steps: [ @@ -897,7 +902,7 @@ namespace pxt.assets.music { }, { - /* hat 4 */ + name: lf("hat 4"), startFrequency: 400, startVolume: 0, steps: [ @@ -929,7 +934,7 @@ namespace pxt.assets.music { }, { - /* double hat */ + name: lf("double hat"), startFrequency: 3500, startVolume: 1024, steps: [ @@ -967,7 +972,7 @@ namespace pxt.assets.music { }, { - /* metallic */ + name: lf("metallic"), startFrequency: 2000, startVolume: 1024, steps: [ @@ -987,7 +992,7 @@ namespace pxt.assets.music { }, { - /* low tom */ + name: lf("low tom"), startFrequency: 200, startVolume: 200, steps: [ @@ -1013,7 +1018,7 @@ namespace pxt.assets.music { }, { - /* mid tom */ + name: lf("mid tom"), startFrequency: 300, startVolume: 200, steps: [ @@ -1039,7 +1044,7 @@ namespace pxt.assets.music { }, { - /* hi tom */ + name: lf("hi tom"), startFrequency: 500, startVolume: 200, steps: [ @@ -1064,7 +1069,7 @@ namespace pxt.assets.music { ] }, { - /* lo tom 2 */ + name: lf("lo tom 2"), startFrequency: 200, startVolume: 1024, steps: [ @@ -1077,7 +1082,7 @@ namespace pxt.assets.music { ] }, { - /* mid tom 2 */ + name: lf("mid tom 2"), startFrequency: 300, startVolume: 1024, steps: [ @@ -1092,7 +1097,7 @@ namespace pxt.assets.music { { - /* hi tom 2 */ + name: lf("hi tom 2"), startFrequency: 400, startVolume: 1024, steps: [ @@ -1107,7 +1112,7 @@ namespace pxt.assets.music { { - /* thump 1 */ + name: lf("thump 1"), startFrequency: 200, startVolume: 1024, steps: [ @@ -1127,7 +1132,7 @@ namespace pxt.assets.music { }, { - /* thump 2 */ + name: lf("thump 2"), startFrequency: 450, startVolume: 1024, steps: [ @@ -1147,7 +1152,7 @@ namespace pxt.assets.music { }, { - /* cymbal */ + name: lf("cymbal"), startFrequency: 2500, startVolume: 1024, steps: [ @@ -1167,7 +1172,7 @@ namespace pxt.assets.music { }, { - /* crash 1 */ + name: lf("crash 1"), startFrequency: 3000, startVolume: 1024, steps: [ @@ -1187,7 +1192,7 @@ namespace pxt.assets.music { }, { - /* crash 2 */ + name: lf("crash 2"), startFrequency: 800, startVolume: 0, steps: [ @@ -1207,7 +1212,7 @@ namespace pxt.assets.music { }, { - /* crash 3 */ + name: lf("crash 3"), startFrequency: 400, startVolume: 0, steps: [ @@ -1227,7 +1232,7 @@ namespace pxt.assets.music { }, { - /* buzzer */ + name: lf("buzzer"), startFrequency: 2000, startVolume: 1024, steps: [ @@ -1244,7 +1249,8 @@ namespace pxt.assets.music { waveform: 16 } ] - },] + }, + ] } ] } diff --git a/theme/piano-roll/piano-roll.less b/theme/piano-roll/piano-roll.less new file mode 100644 index 000000000000..4d89762c45ae --- /dev/null +++ b/theme/piano-roll/piano-roll.less @@ -0,0 +1,187 @@ +.piano-roll { + display: flex; + flex-direction: column; + + --black-key-height: calc(var(--white-key-height) * 0.625); + --sidebar-width: 100px; + + --octave-height: calc(var(--white-key-height) * 7); + + --grid-cell-height: calc(var(--octave-height) / 12); + --grid-cell-width: calc(var(--octave-width) / 16); + + --key-border: 1px solid black; + + --note-event-color: #a3f0c5; + --note-event-border: 2px solid #21905c; + --note-event-text-color: #21905c; + + --white-key-color: white; + --black-key-color: black; + --key-text-color: black; + + --header-background: #f0f0f0; + + --header-height: 2.5rem; + + max-height: 100%; + height: 100%; +} + +.piano-roll-root { + height: 100%; + overflow-y: hidden; + background: var(--pxt-neutral-background1) +} + +.piano-roll .header-container { + height: var(--header-height); + flex-shrink: 0; +} + +.piano-roll .header, .piano-roll .footer { + height: var(--header-height); + display: flex; + flex-direction: row; + gap: 0.5rem; + background-color: var(--header-background); + align-items: center; +} + +.piano-roll .footer { + flex-shrink: 0; + + .music-playback-controls { + .common-button { + height: calc(var(--header-height) - 0.3rem) + } + + .music-undo-redo .common-button { + color: black; + background: none; + border: none; + + &.disabled { + opacity: 0.5; + } + } + } +} + +.piano-roll .header .octave-controls { + display: flex; + flex-direction: row; + align-items: center; + + & > .common-input-wrapper > .common-input-group { + max-width: 2rem; + } +} + +.piano-roll .sidebar-container { + width: var(--sidebar-width); + position: relative; +} + +.piano-roll .content-container { + display: flex; + flex-direction: row; +} + +.octave-sidebar { + user-select: none; + + .key.white { + height: var(--white-key-height); + border-right: var(--key-border); + border-bottom: var(--key-border); + background-color: var(--white-key-color); + color: var(--key-text-color); + display: flex; + align-items: flex-end; + justify-content: flex-end; + + &.active, &.playing { + background-color: var(--note-event-color); + } + } + + .key.black { + height: var(--black-key-height); + width: calc(var(--sidebar-width) * 0.8); + border: var(--key-border); + border-left: none; + background-color: var(--black-key-color); + position: absolute; + margin-top: calc(var(--black-key-height) * -0.5); + + &.active, &.playing { + background-color: var(--note-event-color); + } + } + + .drum { + height: var(--grid-cell-height); + border-bottom: var(--key-border); + width: var(--sidebar-width); + display: flex; + align-items: center; + justify-content: flex-end; + padding-right: 0.25rem; + background-color: var(--white-key-color); + color: var(--key-text-color); + + &.active, &.playing { + background-color: var(--note-event-color); + } + } +} + +.piano-roll .workspace-container { + flex: 1; + overflow-x: hidden; +} + +.piano-roll .workspace { + background-size: var(--octave-width) var(--octave-height); + background-repeat: repeat; + position: relative; +} + +.piano-roll .workspace .note-event { + height: calc(var(--grid-cell-height) + 1px); + position: absolute; + display: flex; + align-items: center; + user-select: none; + + padding-left: 0.25rem; + + font-size: calc(var(--grid-cell-height) * 0.6); + background-color: var(--note-event-color); + border: var(--note-event-border); + color: var(--note-event-text-color); + + cursor: pointer; +} + +.piano-roll .workspace .note-event:hover { + border-color: white; + color: white; +} + +.piano-roll .scroll-container { + max-height: 100%; + width: 100%; + overflow-y: auto; + flex-grow: 1; +} + +.piano-roll .workspace .playhead { + position: absolute; + top: 0; + left: 0; + width: 2px; + height: 100%; + background-color: red; +} \ No newline at end of file diff --git a/theme/pxt.less b/theme/pxt.less index f3a918c0d5d5..93bc940ae5ec 100644 --- a/theme/pxt.less +++ b/theme/pxt.less @@ -49,5 +49,7 @@ @import "react-common"; +@import "piano-roll/piano-roll"; + /* Reference import */ @import (reference) "semantic.less"; diff --git a/webapp/public/asseteditor.html b/webapp/public/asseteditor.html index 5a72ce91a219..223e6b50baa3 100644 --- a/webapp/public/asseteditor.html +++ b/webapp/public/asseteditor.html @@ -19,7 +19,7 @@ - +