[{"data":1,"prerenderedAt":710},["ShallowReactive",2],{"project-vue-piano-roll":3},{"id":4,"title":5,"body":6,"caseStudy":658,"caseStudyType":680,"demoBackground":681,"demoMode":682,"demoUrl":683,"description":684,"extension":685,"headTitle":686,"links":687,"meta":695,"navigation":696,"ogImage":697,"path":699,"refreshLabel":681,"robots":681,"schemaOrg":700,"seo":704,"sitemap":705,"stem":708,"__hash__":709},"projects\u002Fprojects\u002Fvue-piano-roll.md","Vue Piano Roll",{"type":7,"value":8,"toc":653},"minimark",[9,14,18,21,25,386,393,631,634,638,649],[10,11,13],"h2",{"id":12},"overview","Overview",[15,16,17],"p",{},"While working on a project of creative tools for musicians, I found myself in need of an easy way to allow users to input musical ideas into a Vue application. Having a lot of experience with the piano-roll type of interface that you often find in a digital audio workstation (DAW), I decided to create a component that would allow users to create and edit musical ideas in a simple and intuitive way.",[15,19,20],{},"I also wanted to make sure that the component was highly customizable and flexible.",[10,22,24],{"id":23},"component-api","Component API",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-typescript shiki shiki-themes github-dark","export interface PianoRollProps {\n  zoomX?: number;\n  zoomY?: number;\n  rangeBottom?: OctaveNote;\n  rangeTop?: OctaveNote;\n  currentBeat?: number;\n  currentTick?: number;\n  ticksPerBeat?: number;\n  defaultNoteLength?: number;\n  noteHeight?: number;\n  modelValue: PianoRollNote[];\n  length?: number | \"infinite\";\n  loop?: boolean;\n  noteColor?: string;\n  backgroundColor?: string;\n  incidentalColor?: string;\n  gridColor?: string;\n  labelColor?: string;\n  labelIncidentalColor?: string;\n  labelBackgroundColor?: string;\n  labelBorderColor?: string;\n  borderWidth?: number;\n  shadowColor?: string;\n  shadowMap?: ShadowMap[];\n  onNoteEvent?: (event: NoteEvent) => void;\n}\n","typescript","",[33,34,35,55,72,84,97,109,121,133,145,157,169,184,203,216,229,241,253,265,277,289,301,313,325,337,350,380],"code",{"__ignoreMap":31},[36,37,40,44,47,51],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"snl16","export",[36,45,46],{"class":42}," interface",[36,48,50],{"class":49},"svObZ"," PianoRollProps",[36,52,54],{"class":53},"s95oV"," {\n",[36,56,58,62,65,69],{"class":38,"line":57},2,[36,59,61],{"class":60},"s9osk","  zoomX",[36,63,64],{"class":42},"?:",[36,66,68],{"class":67},"sDLfK"," number",[36,70,71],{"class":53},";\n",[36,73,75,78,80,82],{"class":38,"line":74},3,[36,76,77],{"class":60},"  zoomY",[36,79,64],{"class":42},[36,81,68],{"class":67},[36,83,71],{"class":53},[36,85,87,90,92,95],{"class":38,"line":86},4,[36,88,89],{"class":60},"  rangeBottom",[36,91,64],{"class":42},[36,93,94],{"class":49}," OctaveNote",[36,96,71],{"class":53},[36,98,100,103,105,107],{"class":38,"line":99},5,[36,101,102],{"class":60},"  rangeTop",[36,104,64],{"class":42},[36,106,94],{"class":49},[36,108,71],{"class":53},[36,110,112,115,117,119],{"class":38,"line":111},6,[36,113,114],{"class":60},"  currentBeat",[36,116,64],{"class":42},[36,118,68],{"class":67},[36,120,71],{"class":53},[36,122,124,127,129,131],{"class":38,"line":123},7,[36,125,126],{"class":60},"  currentTick",[36,128,64],{"class":42},[36,130,68],{"class":67},[36,132,71],{"class":53},[36,134,136,139,141,143],{"class":38,"line":135},8,[36,137,138],{"class":60},"  ticksPerBeat",[36,140,64],{"class":42},[36,142,68],{"class":67},[36,144,71],{"class":53},[36,146,148,151,153,155],{"class":38,"line":147},9,[36,149,150],{"class":60},"  defaultNoteLength",[36,152,64],{"class":42},[36,154,68],{"class":67},[36,156,71],{"class":53},[36,158,160,163,165,167],{"class":38,"line":159},10,[36,161,162],{"class":60},"  noteHeight",[36,164,64],{"class":42},[36,166,68],{"class":67},[36,168,71],{"class":53},[36,170,172,175,178,181],{"class":38,"line":171},11,[36,173,174],{"class":60},"  modelValue",[36,176,177],{"class":42},":",[36,179,180],{"class":49}," PianoRollNote",[36,182,183],{"class":53},"[];\n",[36,185,187,190,192,194,197,201],{"class":38,"line":186},12,[36,188,189],{"class":60},"  length",[36,191,64],{"class":42},[36,193,68],{"class":67},[36,195,196],{"class":42}," |",[36,198,200],{"class":199},"sU2Wk"," \"infinite\"",[36,202,71],{"class":53},[36,204,206,209,211,214],{"class":38,"line":205},13,[36,207,208],{"class":60},"  loop",[36,210,64],{"class":42},[36,212,213],{"class":67}," boolean",[36,215,71],{"class":53},[36,217,219,222,224,227],{"class":38,"line":218},14,[36,220,221],{"class":60},"  noteColor",[36,223,64],{"class":42},[36,225,226],{"class":67}," string",[36,228,71],{"class":53},[36,230,232,235,237,239],{"class":38,"line":231},15,[36,233,234],{"class":60},"  backgroundColor",[36,236,64],{"class":42},[36,238,226],{"class":67},[36,240,71],{"class":53},[36,242,244,247,249,251],{"class":38,"line":243},16,[36,245,246],{"class":60},"  incidentalColor",[36,248,64],{"class":42},[36,250,226],{"class":67},[36,252,71],{"class":53},[36,254,256,259,261,263],{"class":38,"line":255},17,[36,257,258],{"class":60},"  gridColor",[36,260,64],{"class":42},[36,262,226],{"class":67},[36,264,71],{"class":53},[36,266,268,271,273,275],{"class":38,"line":267},18,[36,269,270],{"class":60},"  labelColor",[36,272,64],{"class":42},[36,274,226],{"class":67},[36,276,71],{"class":53},[36,278,280,283,285,287],{"class":38,"line":279},19,[36,281,282],{"class":60},"  labelIncidentalColor",[36,284,64],{"class":42},[36,286,226],{"class":67},[36,288,71],{"class":53},[36,290,292,295,297,299],{"class":38,"line":291},20,[36,293,294],{"class":60},"  labelBackgroundColor",[36,296,64],{"class":42},[36,298,226],{"class":67},[36,300,71],{"class":53},[36,302,304,307,309,311],{"class":38,"line":303},21,[36,305,306],{"class":60},"  labelBorderColor",[36,308,64],{"class":42},[36,310,226],{"class":67},[36,312,71],{"class":53},[36,314,316,319,321,323],{"class":38,"line":315},22,[36,317,318],{"class":60},"  borderWidth",[36,320,64],{"class":42},[36,322,68],{"class":67},[36,324,71],{"class":53},[36,326,328,331,333,335],{"class":38,"line":327},23,[36,329,330],{"class":60},"  shadowColor",[36,332,64],{"class":42},[36,334,226],{"class":67},[36,336,71],{"class":53},[36,338,340,343,345,348],{"class":38,"line":339},24,[36,341,342],{"class":60},"  shadowMap",[36,344,64],{"class":42},[36,346,347],{"class":49}," ShadowMap",[36,349,183],{"class":53},[36,351,353,356,358,361,364,366,369,372,375,378],{"class":38,"line":352},25,[36,354,355],{"class":49},"  onNoteEvent",[36,357,64],{"class":42},[36,359,360],{"class":53}," (",[36,362,363],{"class":60},"event",[36,365,177],{"class":42},[36,367,368],{"class":49}," NoteEvent",[36,370,371],{"class":53},") ",[36,373,374],{"class":42},"=>",[36,376,377],{"class":67}," void",[36,379,71],{"class":53},[36,381,383],{"class":38,"line":382},26,[36,384,385],{"class":53},"}\n",[15,387,388,389,392],{},"The component uses the ",[33,390,391],{},"v-model"," directive to allow two-way note-data binding between the component and its parent, which means that the note values can be easily accessed to manipulate the data in the parent component or used for playback with any audio library, or processed and exported to a file format such as MIDI.",[26,394,396],{"className":28,"code":395,"language":30,"meta":31,"style":31},"\u002F\u002F GMajor Triad\n[\n  {\n    id: 3,\n    start: 0,\n    length: 1,\n    note: \"D3\",\n    velocity: 100,\n    color: \"#f43f5f\",\n    selected: false,\n  },\n  {\n    id: 2,\n    start: 0,\n    length: 1,\n    note: \"B2\",\n    velocity: 100,\n    color: \"#f43f5f\",\n    selected: true,\n  },\n  {\n    id: 1,\n    start: 0,\n    length: 1,\n    note: \"G2\",\n    velocity: 100,\n    color: \"#f43f5f\",\n    selected: false,\n  },\n];\n",[33,397,398,404,409,414,425,435,445,455,465,475,485,490,494,503,511,519,528,536,544,553,557,561,569,577,585,594,602,611,620,625],{"__ignoreMap":31},[36,399,400],{"class":38,"line":39},[36,401,403],{"class":402},"sAwPA","\u002F\u002F GMajor Triad\n",[36,405,406],{"class":38,"line":57},[36,407,408],{"class":53},"[\n",[36,410,411],{"class":38,"line":74},[36,412,413],{"class":53},"  {\n",[36,415,416,419,422],{"class":38,"line":86},[36,417,418],{"class":53},"    id: ",[36,420,421],{"class":67},"3",[36,423,424],{"class":53},",\n",[36,426,427,430,433],{"class":38,"line":99},[36,428,429],{"class":53},"    start: ",[36,431,432],{"class":67},"0",[36,434,424],{"class":53},[36,436,437,440,443],{"class":38,"line":111},[36,438,439],{"class":53},"    length: ",[36,441,442],{"class":67},"1",[36,444,424],{"class":53},[36,446,447,450,453],{"class":38,"line":123},[36,448,449],{"class":53},"    note: ",[36,451,452],{"class":199},"\"D3\"",[36,454,424],{"class":53},[36,456,457,460,463],{"class":38,"line":135},[36,458,459],{"class":53},"    velocity: ",[36,461,462],{"class":67},"100",[36,464,424],{"class":53},[36,466,467,470,473],{"class":38,"line":147},[36,468,469],{"class":53},"    color: ",[36,471,472],{"class":199},"\"#f43f5f\"",[36,474,424],{"class":53},[36,476,477,480,483],{"class":38,"line":159},[36,478,479],{"class":53},"    selected: ",[36,481,482],{"class":67},"false",[36,484,424],{"class":53},[36,486,487],{"class":38,"line":171},[36,488,489],{"class":53},"  },\n",[36,491,492],{"class":38,"line":186},[36,493,413],{"class":53},[36,495,496,498,501],{"class":38,"line":205},[36,497,418],{"class":53},[36,499,500],{"class":67},"2",[36,502,424],{"class":53},[36,504,505,507,509],{"class":38,"line":218},[36,506,429],{"class":53},[36,508,432],{"class":67},[36,510,424],{"class":53},[36,512,513,515,517],{"class":38,"line":231},[36,514,439],{"class":53},[36,516,442],{"class":67},[36,518,424],{"class":53},[36,520,521,523,526],{"class":38,"line":243},[36,522,449],{"class":53},[36,524,525],{"class":199},"\"B2\"",[36,527,424],{"class":53},[36,529,530,532,534],{"class":38,"line":255},[36,531,459],{"class":53},[36,533,462],{"class":67},[36,535,424],{"class":53},[36,537,538,540,542],{"class":38,"line":267},[36,539,469],{"class":53},[36,541,472],{"class":199},[36,543,424],{"class":53},[36,545,546,548,551],{"class":38,"line":279},[36,547,479],{"class":53},[36,549,550],{"class":67},"true",[36,552,424],{"class":53},[36,554,555],{"class":38,"line":291},[36,556,489],{"class":53},[36,558,559],{"class":38,"line":303},[36,560,413],{"class":53},[36,562,563,565,567],{"class":38,"line":315},[36,564,418],{"class":53},[36,566,442],{"class":67},[36,568,424],{"class":53},[36,570,571,573,575],{"class":38,"line":327},[36,572,429],{"class":53},[36,574,432],{"class":67},[36,576,424],{"class":53},[36,578,579,581,583],{"class":38,"line":339},[36,580,439],{"class":53},[36,582,442],{"class":67},[36,584,424],{"class":53},[36,586,587,589,592],{"class":38,"line":352},[36,588,449],{"class":53},[36,590,591],{"class":199},"\"G2\"",[36,593,424],{"class":53},[36,595,596,598,600],{"class":38,"line":382},[36,597,459],{"class":53},[36,599,462],{"class":67},[36,601,424],{"class":53},[36,603,605,607,609],{"class":38,"line":604},27,[36,606,469],{"class":53},[36,608,472],{"class":199},[36,610,424],{"class":53},[36,612,614,616,618],{"class":38,"line":613},28,[36,615,479],{"class":53},[36,617,482],{"class":67},[36,619,424],{"class":53},[36,621,623],{"class":38,"line":622},29,[36,624,489],{"class":53},[36,626,628],{"class":38,"line":627},30,[36,629,630],{"class":53},"];\n",[632,633],"hr",{},[10,635,637],{"id":636},"try-it","Try it",[15,639,640,641,648],{},"The example above is interactive, so you can try it out by dragging existing notes, clicking to add notes, right-clicking to delete existing notes, or dragging the edges of notes to resize them. You can also have a look at the ",[642,643,647],"a",{"href":644,"rel":645},"https:\u002F\u002Fvue-piano-roll.netlify.app\u002F",[646],"nofollow","demo hosted on Netlify",", where you can change the tempo, the instrument, and inspect the component's output.",[650,651,652],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":31,"searchDepth":57,"depth":57,"links":654},[655,656,657],{"id":12,"depth":57,"text":13},{"id":23,"depth":57,"text":24},{"id":636,"depth":57,"text":637},{"role":659,"year":660,"status":661,"disciplines":662,"stack":666,"challenge":671,"outcome":672,"primaryAction":673,"demoInstructions":676,"related":677},"Creator & maintainer","2022—present","Published open-source component",[663,664,665],"Music tooling","Component design","Developer experience",[667,668,669,670],"Vue","Nuxt","TypeScript","Web Audio","Make a familiar DAW interaction feel immediate in the browser while keeping its API flexible for other applications.","A configurable Vue piano-roll component, published on npm with a live, editable demonstration.",{"title":674,"link":644,"source":675},"Open live demo","website","Try it: drag notes to move them, drag an edge to resize, click to add a note, and right-click to remove one.",{"label":678,"to":679},"Next: explore the fiction-word generator","\u002Fdev\u002Ffiction-word","Development case study",null,"iframe","https:\u002F\u002Fvue-piano-roll.netlify.app\u002Fsimple","Vue Piano Roll is a flexible piano roll component for creating and editing musical notes.","md","ATH: Dev - Vue Piano Roll",[688,691],{"link":689,"source":690},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-piano-roll","npm",{"title":692,"link":693,"source":694},"GitHub","https:\u002F\u002Fgithub.com\u002Fhowardah\u002Fvue-piano-roll","github",{},true,{"url":698},"\u002Fimages\u002Fdev\u002Fvue-piano-roll.webp","\u002Fprojects\u002Fvue-piano-roll",[701],{"name":5,"applicationCategory":702,"@type":703},"DeveloperApplication","SoftwareApplication",{"title":5,"description":684},{"loc":699,"changefreq":706,"priority":707},"monthly",0.7,"projects\u002Fvue-piano-roll","dq61v7rese48eIeeYcPMriH3ocHbUju0RlRVp8i2nKg",1786815543650]