/Downbeat

🎹 Small playable virtual piano, written in C++

Primary LanguageC++GNU General Public License v3.0GPL-3.0


Small playable virtual piano, written in C++ - (POO, an I, sem. II)

pic

Features

  • Play notes using mouse clicks/keyboard
  • Press 1 or more notes at the same time
  • Record and save your songs
  • Load and play saved songs
  • Intuitive interface
  • Runs on Linux/Mac

Installation

Please install Zenity (dialog boxes) and SFML (GUI):

sudo apt install zenity libsfml-dev

Then, in the root project folder, run make to compile the code.

Run using make run.


Course tasks

Cerințe obligatorii:
🎹 Interfața grafica (în SDL, SFML, Qt, etc) cu API pentru C++ ☑️
🎹 RTTI ☑️

piano.cpp

if (dynamic_cast<WhiteKey *>(key)) {

    key->setPosition(whiteX, Y);
    whiteX += 48;
}

🎹 Abstract Classes ☑️

Key.h

class Key {

    // ...
    
    virtual sf::Sprite& getSprite() = 0;
    virtual void playSound() = 0;
    virtual void setPosition(int x, int y) = 0;
};

🎹 Operatori (minim 4 * numărul oamenilor din echipa) ☑️

Settings.cpp

// Populates json with default settings, if empty :)
Settings& Settings::operator++(int) {
    if(std::ifstream f("config.txt"); f.peek() == std::ifstream::traits_type::eof())
        generateConfig();
    return *this;
}
Piano.cpp

// Shows piano data.
std::ostream& operator<<(std::ostream& out, std::shared_ptr<Piano> piano) {
    out << "Pian cu "<< piano->getKeys().size() <<" clape.\n";
    for(auto &i : piano->getKeys()) {
        // ...
    }
    return out;
 }
Piano.cpp

// Operator care acceseaza direct elementele vectorului vector<Key>piano, din clasa Piano
std::shared_ptr<Key> Piano::operator[](int i) {
    return piano[i];
}
Key.cpp

// operator care returneaza id-ul notei.
int& Key::operator[](std::string s)
{
    if (s == "noteId")
        return noteId;
    
}

🎹 Heap Memory Allocation ☑️

piano.cpp

pianoKeys.push_back(std::shared_ptr<WhiteKey>(new WhiteKey(tKeyWhite, tKeyWhitePressed,i,std::to_string(i)+".ogg")));

🎹 Exceptions ☑️

ObjException.h

#include <exception>
class ObjException : public std::exception
{
    const char *what() const throw()
    {
        return "bad object in piano array";
    }
};
Piano.cpp

try
  {
      if (dynamic_cast<WhiteKey *>(key.get()))
      {
       // ...
      }
      else
          throw ObjException();
  }
  catch (std::exception &e)
  {
      std::cerr << e.what();
  }

🎹 STL ☑️

#include <string>
#include <vector>
#include <exception>
#include <unordered_set>
#include <memory>

🎹 Lambda expressions ☑️

MainWindow.cpp

auto getKeyPressed = [=]() {
    return piano->findKeyPressed(settings.getKbKeys()[std::to_string(event.key.code)]);
};

// usage
if (event.type == sf::Event::KeyPressed)
{
    if (settings.getKbKeys().contains(std::to_string(event.key.code)))
    {

        activeKey = getKeyPressed();
        Recorder::log("down", activeKey->getId());
        // ...
    }
}

🎹 Templates ☑️

MainWindow.cpp - Definition

template <class T>
void MainWindow::checkPressed(T &obj)
{
    try
    {
        if (!dynamic_cast<Button *>(&obj)) //if not button
            throw TypeException();
        if (obj.getSprite().getGlobalBounds().contains(mapPixelToCoords(sf::Mouse::getPosition((*this)))))
            obj.press(true);
    }
    catch (std::exception &e)
    {
        std::cerr << e.what();
    }
}
MainWindow.cpp - Usage

if (activeKey == nullptr)
  {
      // Button click logic :D
      checkPressed<ButtonRecord>(buttonRecord);
      checkPressed<ButtonLoad>(buttonLoad);
      checkPressed<ButtonPlay>(buttonPlay);
  }
  else
  {
      activeKey->setPressed(true);

      Recorder::log("down", activeKey->getId());
  }

🎹 Smart pointers - minim 1 tip / proiect ☑️

Piano.cpp

static std::shared_ptr<Piano> instance;
std::vector<std::shared_ptr<Key>> piano;

... and many more!

🎹 Design patterns - minim 2 ☑️

Singleton ☑️

/**
 * Piano: Singleton class that holds array of piano keys
 * Calculates black/white key positions.
 * Holds key textures.
 */
class Piano
{
  // ...
  public:
      static Piano *getInstance()
      {
          if (!instance){
              instance = new Piano;
              std::cout<<"pianul a fost creat"<<std::endl;
          }
          else{
              std::cout<<"pianul a fost invocat"<<std::endl;
          }
          return instance;
      }

Facade ☑️

// Recorder.h - facade that hides all recording logic.
class Recorder {
    static json songData;
    static sf::Clock clock;
    static bool recording;
    public:
    Recorder();
    static void start();
    static void stop();
    static void save();
    static void log(std::string type, int noteId);
    static void play();
    static void load(std::string path);
    static bool isRecording();
};

🎹 Features of C++17/20 (constexpr, consteval, constinit, fold expressions, init statement for if/switch, etc) ☑️

WhiteKey.cpp

//// Init statement for if (C++17) ////
if (double seconds = clock.getElapsedTime().asMilliseconds(); seconds < 100)
{
    //Atenuare sunet :) onKeyReleased
    c = std::lerp(100, 0, seconds / 100.0);
    //std::cout << seconds << " " << c << std::endl;
    sound.setVolume(c);
    if (c <= 5.0)
        break;
}

Opțional => Bonus:
🎹 Move semantics
🎹 Multithreading (la echipe de 3-4 oameni)
🎹 Computer Network / Socket Programming (la echipe de 2-4 oameni)
🎹 BD