Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Flexible Direct2D Pianoroll for Your Music Apps

0.00/5 (No votes)
9 May 2019 1  
Create music easily

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:

// Instantiate
PR::PIANOROLL prx;

// Pass messages WM_KEYDOWN, WM_LBUTTONDBLCLK, WM_LBUTTONDOWN, 
// WM_RBUTTONDOWN, WM_MOUSEMOVE,WM_LBUTTONUP, WM_SYSKEYDOWN 
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; // C
        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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here