Introduction
This is one of my oldest projects, now available as an open source. A piano roll is an easy way to compose music without knowledge of musical score elements.
Featuring:
- Notes support channel (0-15), velocity (0-127) , layer (unlimited)
- Moving, Resizing with snap controls
- Supports diatonic movement through specified Key and Mode (Major/Minor)
- Unlimited undo/redo
- Unlimited layers
- Piano side (Left/Right/Bottom/None)
- Callbacks
- Tools - Auto, Eraser, Single click entry, Quantizer
- Serialization/Deserialization to XML
- Key per measure
- Tempo per measure
- Time signature per measure
- MIDI export
- Partial MIDI import (in progress)
- Non-note MIDI notes
- Meta events (Raw hex and specific items)
- Aftertouch events
- Pitch shift events
- Note quantization
- Markers
- Long text show
- Chromatic/Diatonic transposes
- Record from MIDI in device
- Streaming callback
- Configurable colors
- Part mode
The sample project demonstrates:
- The control
- Save/Load XML
- Save MIDI
- Play at run time through midi Out
- Record from midi In
You can also check my roll example in VSTX to see a tool that composes music through VST instruments, using this pianoroll.
Using Pianoroll
All you need is to include "pianoroll.hpp" to your project, then:
PR::PIANOROLL prx;
switch(uMessage)
{
case WM_KEYDOWN:
prx.Message(uMessage,wParam,lParam);
}
Callbacks
class PIANOROLLCALLBACK
{
public:
virtual HRESULT NoteAdded(PIANOROLL* pr, NOTE*) = 0;
virtual HRESULT NoteRemoved(PIANOROLL* pr, NOTE*) = 0;
virtual void RedrawRequest(PIANOROLL* pr, unsigned long long param) = 0;
virtual HRESULT OnNoteChange(PIANOROLL* pr, NOTE* oldn, NOTE* newn) = 0;
virtual HRESULT OnNoteSelect(PIANOROLL* pr, NOTE* oldn, bool) = 0;
virtual void OnPianoOn(PIANOROLL*, int n) = 0;
virtual void OnPianoOff(PIANOROLL*, int off) = 0;
};
prx.AddCallback(myPrc);
The mandatory callback is the RedrawRequest
. When this function is called, call PIANOROLL::Paint()
, passing your ID2D1RenderTarget
so the control redraws itself.
Measures, Keys and Times
Key setting is used to allow the control to move notes within a specific scale (if you hold Shift while moving, notes are moved chromatically instead). For example, if the current key is D major and you press the minus key on a D note, this moves to C#.
Times allows a measure to have a different number of beats (default 4).
Keys and beats are a per measure setting. The interesting thing in the Key is the scale creation function which uses the known MMmMMMm
or MmMMm3m
formats for major/minor to create the scale based on the key:
void CreateScale()
{
Scale.clear();
unsigned int fi = 0x48;
if (k > 0)
fi = (7 * k) % 12;
if (m == 1)
fi -= 3;
fi = fi % 12;
if (m == 1)
{
Scale.push_back(fi);
Scale.push_back(fi + 2);
Scale.push_back(fi + 3);
Scale.push_back(fi + 5);
Scale.push_back(fi + 7);
Scale.push_back(fi + 8);
Scale.push_back(fi + 11);
}
else
if (m == 0)
{
Scale.push_back(fi);
Scale.push_back(fi + 2);
Scale.push_back(fi + 4);
Scale.push_back(fi + 5);
Scale.push_back(fi + 7);
Scale.push_back(fi + 9);
Scale.push_back(fi + 11);
}
for (auto& e : Scale)
e = e % 12;
}
Layers
Notes can belong to a certain layer (the default is layer 1). When working with layers, notes that belong to a layer different than the current layer cannot be modified except by the command that changes layers.
Notes
Notes have configurable velocity, channel and layer. Notes have a starting position (measure + beat) and a duration:
class NOTE
{
public:
int midi = 0;
int Selected = 0;
POSITION p;
FRACTION d;
int vel = 127;
int ch = 0;
int layer = 0;
}
In addition, notes can contain a non note event, like an instrument patch, pitch bend, sysex event or controller message. When using events which carry no note information, you can put them anywhere (use control+ double click).
Piano
The control creates a simple piano that allows you to test notes. The piano can be on side (left/right) or at the bottom. The control also provides a piano-only drawing (this is used in my VSTX library for VST instruments testing).
Drawing Technology
The control uses your own Direct2D rendering target, so you can draw it wherever you like (as a HWND
or even as part of an image for a screenshot or in a printer HDC
).
Learn more about Direct2D
in my older article.
The control will notify you when it needs redrawing.
Zooming, Moving, Snapping and Scrolling
The control provides unlimited moving between measures and unlimited zoom in or zoom out. The control also allows note moving to be snapped to beats. This is configurable to allow denominations of the beat in order to move more precisely. If you want non snapping while moving, move the notes while holding Shift. You can scroll using the arrow keys or the top scroll bar.
Streaming
The control can output a vector of streamable notes (which have absolute time formats) for your midi sequencer.
Fractions
All internal manipulations of the library are done with fractions, so no information is lost on floating point operations.
class FRACTION
{
public:
ssize_t n = 0;
ssize_t d = 1;
... operators to multiply, add, compare etc
}
Transposing
Chromatic transposing is easy, we add or remove 1 midi note to a note. Diatonic transposition is more complex, we have to take the current key and mode into accout and then check if the note belongs to the new scale.
Serialization
The control uses my XML library to serialize. You can then reload the control as needed.
MIDI
A simple MIDI class is provided with the pianoroll
that allows to write MIDI file data. MIDI writes have a variable length format:
void WriteVarLen(long value, vector<unsigned char>& b)
{
unsigned long long buffer = value & 0x7f;
while ((value >>= 7) > 0)
{
buffer <<= 8;
buffer |= 0x80;
buffer += (value & 0x7f);
}
for (;;)
{
b.push_back((char)buffer);
if (buffer & 0x80)
buffer >>= 8;
else
break;
}
}
The pianoroll
creates a multiple-track MIDI file (each layer becomes a track). Keys, text, markers, time changes and tempo changes are included in the file.
Keyboard
- A : auto tool
- E : eraser tool
- I : single entry tool
- Q : quantize tool
- 1,2,3,4 (up row) : Select beat duration for next note
- Shift+1,2,3,4 (up row) : Beat duration 1/8, 1/16, 1/32, 1/64
- < and > : Change selected items velocity
- Ctrl+ < and > : Velocity off/full
- < and > when pitch shift: Up/Down pitch shift (combine with Ctrl/Shift/Alt)
- +/- : Change selected items position diatonically
- Shift +/- : Change selected items position chromatically
- Numpad +/-/* : Zoom in,out,all
- Shift + Arrows up/down : Change channel
- Alt + Arrows up/down : Change layer
- / and \ : Enlarge/reduce notes
- D,H,': Double/Half/+1/2 notes
- Ctrl+Q : Quantize notes
- Ctrl+G : Go to measure
- J : Join notes
- Ctrl+1...6 : Snapping resolution
- Alt+1-9 Numpad : Toggle layers
- 1-9 Numpad : Next layer
- Ctrl+A : Select all
- Ctrl+C : Copy
- Ctrl+T : Diatonic transpose
- Ctrl+Shift+T : Chromatic transpose
- Ctrl+V : Paste to last clicked measure
- Ctrl+Z : Undo
- Ctrl+Y : Redo
- [,] : Next/Prev marker
- Right/Left arrow : move roll
- Del : Delete selected notes
- Ctrl+Home : Scroll to start
- X,Z : Move left,right
- P : Toggle part mode
- Alt+P : Next part
Mouse
- Dblclick : insert note (auto tool)
- Control + Dblclick : insert non note event
- Control + Shift + Dblclick : insert aftertouch
- Dblclick on note : remove note
- Click on note : select/unselect (quantize in quantize tool)
- Drag outside note: select (insert in single entry tool, delete in eraser tool, quantize in quantize tool)
- Drag/Resize note
History
- 9/5/2019: More controls, quantization
- 5/5/2019: keyboard shortcuts, markers, pitch bend, after touch, patch and other new features.
- 1/5/2019: First release