Andromeda
Andromeda Blogs

Andromeda Blogs

Creating a simple piano app in C

Photo by Tadas Mikuckis on Unsplash

Creating a simple piano app in C

Implementation of a simple piano app in C without external libs

Andromeda's photo
Andromeda
·Oct 26, 2022·

22 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

  • Introduction
  • Design Plan
  • Adding headers
  • main() function
  • Audio
  • getFrequency function
  • constructFrequency function

Introduction

I am a JavaScript/TypeScript developer in general and not a C programmer. So, I might be doing things in improper way, but I will try to explain it in the best way I can.

In this article, I will teach you how to build a super simple piano app in C. This app only works on windows. The app is implemented without using external libraries. Our app only contains the following headers:

#include<stdio.h>
#include<conio.h>
#include<windows.h>
#include<stdlib.h>

Design Plan

Our piano app will use the following layout:

 ___________________________________________________________________________
|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |
|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |
|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |
|  |S| |D|  |  |G| |H| |J|  |  |2| |3|  |  |5| |6| |7|  |  |9| |0|  |  |=|  |
|  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_|  |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
| Z | X | C | V | B | N | M | Q | W | E | R | T | Y | U | I | O | P | [ | ] |
|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|

This layout was inspired by FL Studio. So, let's get started.

Adding headers

#include<stdio.h>
#include<conio.h>
#include<windows.h>
#include<stdlib.h>

We will use the APIs included in the headers above. stdio.h will enable us to read/write to standard input/output. Similarly, we will use conio.h to get keypress value. windows.h will be used to produce the sound and stdlib.h will provide some utilities such as system function.

main() function

Ok so let's begin by writing the main function components. The main() function in C is the entry point of our program. This function is invoked upon executing our code. In main function, we need the following things:

  • The duration in ms to play each keystroke
  • Getting the key code value when a key is pressed
  • Playing piano note based on which key is pressed
void main() {
    // keycode will hold the keypress value and duration will be the amount of time to play the audio in milliseconds, with default value as 500ms or half a second
    int keycode, dur = 500;
}

Now, let's read the duration input.

void main() {
    int keycode, dur = 500;

    printf("\nEnter keypress duration in ms: ");
    scanf("%d", &dur);

    if (dur < 1) dur = 500;
}

We can now start implementing the keycode logic. We run endless loop and wait for keypress to occur. Since our program flow is synchronous, the app will halt on the initial iteration until a key is pressed. This allows us to play for infinite amount of time.

Our while loop should look something like this:

// while(1) is basically while(true)
while(1) {
        // we can identify which key was pressed using the getch function
        keycode = getch();

        // we should update some keys here, just for the easy access
        // The white keys to update
        if (keycode == 44) keycode = 113;
        else if (keycode == 46) keycode = 119;
        else if (keycode == 47) keycode = 101;

        // The black keys to update
        else if (keycode == 108) keycode = 50;
        else if (keycode == 59) keycode = 51;
}

Here is a simple list of keycode map:

Key

Key value

Key

Key value

Alt

18

F5

116

Arrow Down

40

F6

117

Arrow Left

37

F7

118

Arrow Right

39

F8

119

Arrow Up

38

F9

120

Backspace

8

F10

121

Caps Lock

20

F1

122

Ctrl

17

F12

123

Delete

46

Home

36

End

35

Insert

45

Enter

13

Num Lock

144

Esc

27

(NumPad) -

109

F1

112

(NumPad) *

106

F2

113

(NumPad) .

110

F3

114

(NumPad) /

111

F4

115

(NumPad) +

107

(NumPad) 0

96

P

80

NumPad) 1

97

Q

81

(NumPad) 2

98

R

82

NumPad) 3

99

S

83

(NumPad) 4

100

T

84

(NumPad) 5

101

U

85

(NumPad) 6

102

V

86

(NumPad) 7

103

W

87

(NumPad) 8

104

X

88

(NumPad) 9

105

Y

89

Page Down

34

Z

90

Page Up

33

1

49

Pause

19

2

50

Print Scrn

44

3

51

Scroll Lock

145

4

52

Shift

16

5

53

Spacebar

32

6

54

Tab

9

7

55

A

65

8

56

B

66

9

57

C

67

0

48

D

68

'

222

E

69

-

189

F

70

,

188

G

71

.

190

H

72

/

191

I

73

;

186

J

74

[

219

K

75

\

220

L

76

]

221

M

77

'

192

N

78

=

187

O

79

  

You can shape it however you'd like.

Let's implement exit and restart logic as well. For exit, we will listen to ctrl+c keystroke and ctrl+r for restart.

First, let's add a reset label at the top so we can jump to the beginning of the program on restart.

void main() {
    int keycode, dur = 500;

    resetLabel:
    printf("\nEnter keypress duration in ms: ");
    // ...

Now, let's check for exit and restart keystrokes inside our while loop.

while(1) {
        // exit
        if (keycode == 3) break;

        // restart
        else if (keycode == 18) {
            keycode = 0;
            system("cls");
            goto resetLabel;
        }

        keycode = getch();

        if (keycode == 44) keycode = 113;
        else if (keycode == 46) keycode = 119;
        else if (keycode == 47) keycode = 101;
        else if (keycode == 108) keycode = 50;
        else if (keycode == 59) keycode = 51;
}

We can also print out our piano layout.

void main() {
    int keycode, dur = 500;

    resetLabel:
    printf("\nEnter keypress duration in ms: ");
    scanf("%d", &dur);

    if (dur < 1) dur = 500;

    printf("\nApp started! Press ctrl+c to exit & ctrl+r to restart!\n\n");

    printf(" ___________________________________________________________________________ \n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  |S| |D|  |  |G| |H| |J|  |  |2| |3|  |  |5| |6| |7|  |  |9| |0|  |  |=|  |\n");
    printf("|  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_|  |\n");
    printf("|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
    printf("|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
    printf("| Z | X | C | V | B | N | M | Q | W | E | R | T | Y | U | I | O | P | [ | ] |\n");
    printf("|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|\n\n");

    // ...

Now, we have a program that listens to our keystrokes.

Audio

Piano notes have different frequencies. We have to map out our keystrokes and construct a piano note's frequency. First of all, let's create a makeSound function and pass keystroke and duration to it. We also need to implement getFrequency function side by side.

int makeSound(int keycode, int dur) {
    float freq = getFrequency(keycode);
    if (freq < 1) return 0;

    // Beep is exposed by windows header file
    return Beep(freq, dur);
}

Let's call makeSound inside our while loop.

while(1) {
        if (keycode == 3) break;

        else if (keycode == 18) {
            keycode = 0;
            system("cls");
            goto resetLabel;
        }

        keycode = getch();

        if (keycode == 44) keycode = 113;
        else if (keycode == 46) keycode = 119;
        else if (keycode == 47) keycode = 101;
        else if (keycode == 108) keycode = 50;
        else if (keycode == 59) keycode = 51;
        makeSound(keycode, dur);
}

Our entire main function looks like this now:

void main() {
    int keycode, dur = 500;

    resetLabel:
    printf("\nEnter keypress duration in ms: ");
    scanf("%d", &dur);

    if (dur < 1) dur = 500;

    printf("\nApp started! Press ctrl+c to exit & ctrl+r to restart!\n\n");

    printf(" ___________________________________________________________________________ \n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  |S| |D|  |  |G| |H| |J|  |  |2| |3|  |  |5| |6| |7|  |  |9| |0|  |  |=|  |\n");
    printf("|  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_|  |\n");
    printf("|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
    printf("|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
    printf("| Z | X | C | V | B | N | M | Q | W | E | R | T | Y | U | I | O | P | [ | ] |\n");
    printf("|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|\n\n");

    while(1) {
        if (keycode == 3) break;
        else if (keycode == 18) {
            keycode = 0;
            system("cls");
            goto resetLabel;
        }

        keycode = getch();

        if (keycode == 44) keycode = 113;
        else if (keycode == 46) keycode = 119;
        else if (keycode == 47) keycode = 101;
        else if (keycode == 108) keycode = 50;
        else if (keycode == 59) keycode = 51;
        makeSound(keycode, dur);
    }
}

getFrequency function

Along with getFrequency we need to implement some simple DSA functions to make our life easier with arrays. Inside getFrequency, we create a mapping of the notes based on keystrokes. These are our keycode values:

// white notes
int c3[7] = {122, 120, 99, 118, 98, 110, 109};
int c4[7] = {113, 119, 101, 114, 116, 121, 117};
int c5[5] = {105, 111, 112, 91, 93};

// back notes
int c3f[5] = {115, 100, 103, 104, 106};
int c4f[5] = {50, 51, 53, 54, 55};
int c5f[3] = {57, 48, 61};

We begin from C3 octave and terminate at C5 octave.

Let's create a simple function to check if the given array has the specified element and another similar function to return the index of that element. We will name these functions has and indexOf.

has

In has function, we simply iterate over the given array and if the given element is found on that array, we return 1 and 0 upon failure.

int has(int num, int nums[], int len) {
    for (int i = 0; i < len; i++) {
        if (nums[i] == num) return 1;
    }
    return 0;
}

indexOf

In indexOf function, we iterate over the given array like we did in has function but instead of returning 1 and 0, we return the index of that element. Upon failure, we return a negative value -1.

int indexOf(int arr[], int findIndex, int len) {
    for (int i = 0; i < len; i++) {
        if (arr[i] == findIndex) return i;
    }

    return -1;
}

Now for getFrequency, we can implement it in this way:

float getFrequency(int keycode) {
    float frequency = 0;
    char *note = "Unknown";

    // white notes
    int c3[7] = {122, 120, 99, 118, 98, 110, 109};
    int c4[7] = {113, 119, 101, 114, 116, 121, 117};
    int c5[5] = {105, 111, 112, 91, 93};

    // back notes
    int c3f[5] = {115, 100, 103, 104, 106};
    int c4f[5] = {50, 51, 53, 54, 55};
    int c5f[3] = {57, 48, 61};

    char *wnotes[7] = {"C", "D", "E", "F", "G", "A", "B"};
    char *bnotes[7] = {"C#", "D#", "F#", "G#", "A#"};

    // black
    if (has(keycode, c3f, 5)) {
        int idx = indexOf(c3f, keycode, 5);

        frequency = constructFrequency(220, bnotes[idx]);
        note = bnotes[idx];
    } else if (has(keycode, c4f, 5)) {
        int idx = indexOf(c4f, keycode, 5);

        frequency = constructFrequency(440, bnotes[idx]);
        note = bnotes[idx];
    } else if (has(keycode, c5f, 3)) {
        int idx = indexOf(c5f, keycode, 3);

        frequency = constructFrequency(880, bnotes[idx]);
        note = bnotes[idx];
    }

    // white
    else if (has(keycode, c3, 7)) {
        int idx = indexOf(c3, keycode, 7);

        frequency = constructFrequency(220, wnotes[idx]);
        note = wnotes[idx];
    } else if (has(keycode, c4, 7)) {
        int idx = indexOf(c4, keycode, 7);

        frequency = constructFrequency(440, wnotes[idx]);
        note = wnotes[idx];
    } else if (has(keycode, c5, 5)) {
        int idx = indexOf(c5, keycode, 5);

        frequency = constructFrequency(880, wnotes[idx]);
        note = wnotes[idx];
    }

    return frequency;
}

It simply compares current keystroke value with the note and calls constructFrequency function with frequency initialization value and note name.

constructFrequency function

The constructFrequency function is responsible for generating piano note frequencies. It accepts node value and notes array.

We compare the note value with piano note and construct a frequency based on that. If the note is C, we multiply the node with 0.5946 which gives us the frequency of the note C. Now for the high/low note, we have already implemented that inside getFrequency function. It generates the intensity of the note and passes it to getFrequency.

float constructFrequency(int node, char *note) {
    float base = node, freq;

    if (note == "C") {
        freq = base * 0.5946;
    } else if (note == "C#") {
        freq = base * 0.6299;
    } else if (note == "D") {
        freq = base * 0.6674;
    } else if (note == "D#") {
        freq = base * 0.7071;
    } else if (note == "E") {
        freq = base * 0.7491;
    } else if (note == "F") {
        return base * 0.7937;
    } else if (note == "F#") {
        freq = base * 0.8408;
    } else if (note == "G") {
        freq = base * 0.8908;
    } else if (note == "G#") {
        freq = base * 0.9438;
    } else if (note == "A") {
        freq = base;
    } else if (note == "A#") {
        freq = base * 1.0594;
    } else if (note == "B") {
        freq = base * 1.1224;
    } else {
        freq = 0;
    }

    return freq;
}

After all the calculations above, our piano is basically ready. Here is the final code of this project:

#include<stdio.h>
#include<conio.h>
#include<windows.h>
#include<stdlib.h>

int has(int num, int nums[], int len) {
    for (int i = 0; i < len; i++) {
        if (nums[i] == num) return 1;
    }
    return 0;
}

int indexOf(int arr[], int findIndex, int len) {
    for (int i = 0; i < len; i++) {
        if (arr[i] == findIndex) return i;
    }

    return -1;
}

float constructFrequency(int node, char *note) {
    float base = node, freq;

    if (note == "C") {
        freq = base * 0.5946;
    } else if (note == "C#") {
        freq = base * 0.6299;
    } else if (note == "D") {
        freq = base * 0.6674;
    } else if (note == "D#") {
        freq = base * 0.7071;
    } else if (note == "E") {
        freq = base * 0.7491;
    } else if (note == "F") {
        return base * 0.7937;
    } else if (note == "F#") {
        freq = base * 0.8408;
    } else if (note == "G") {
        freq = base * 0.8908;
    } else if (note == "G#") {
        freq = base * 0.9438;
    } else if (note == "A") {
        freq = base;
    } else if (note == "A#") {
        freq = base * 1.0594;
    } else if (note == "B") {
        freq = base * 1.1224;
    } else {
        freq = 0;
    }

    return freq;
}

float getFrequency(int keycode) {
    float frequency = 0;
    char *note = "Unknown";

    // white notes
    int c3[7] = {122, 120, 99, 118, 98, 110, 109};
    int c4[7] = {113, 119, 101, 114, 116, 121, 117};
    int c5[5] = {105, 111, 112, 91, 93};

    // back notes
    int c3f[5] = {115, 100, 103, 104, 106};
    int c4f[5] = {50, 51, 53, 54, 55};
    int c5f[3] = {57, 48, 61};

    char *wnotes[7] = {"C", "D", "E", "F", "G", "A", "B"};
    char *bnotes[7] = {"C#", "D#", "F#", "G#", "A#"};

    // black
    if (has(keycode, c3f, 5)) {
        int idx = indexOf(c3f, keycode, 5);

        frequency = constructFrequency(220, bnotes[idx]);
        note = bnotes[idx];
    } else if (has(keycode, c4f, 5)) {
        int idx = indexOf(c4f, keycode, 5);

        frequency = constructFrequency(440, bnotes[idx]);
        note = bnotes[idx];
    } else if (has(keycode, c5f, 3)) {
        int idx = indexOf(c5f, keycode, 3);

        frequency = constructFrequency(880, bnotes[idx]);
        note = bnotes[idx];
    }

    // white
    else if (has(keycode, c3, 7)) {
        int idx = indexOf(c3, keycode, 7);

        frequency = constructFrequency(220, wnotes[idx]);
        note = wnotes[idx];
    } else if (has(keycode, c4, 7)) {
        int idx = indexOf(c4, keycode, 7);

        frequency = constructFrequency(440, wnotes[idx]);
        note = wnotes[idx];
    } else if (has(keycode, c5, 5)) {
        int idx = indexOf(c5, keycode, 5);

        frequency = constructFrequency(880, wnotes[idx]);
        note = wnotes[idx];
    }

    return frequency;
}

int makeSound(int keycode, int dur) {
    float freq = getFrequency(keycode);
    if (freq < 1) return 0;

    return Beep(freq, dur);
}

void main() {
    int keycode, dur = 500;

    resetLabel:
    printf("\nEnter keypress duration in ms: ");
    scanf("%d", &dur);

    if (dur < 1) dur = 500;

    printf("\nApp started! Press ctrl+c to exit & ctrl+r to restart!\n\n");

    printf(" ___________________________________________________________________________ \n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  | | | |  |  | | | | | |  |  | | | |  |  | | | | | |  |  | | | |  |  | |  |\n");
    printf("|  |S| |D|  |  |G| |H| |J|  |  |2| |3|  |  |5| |6| |7|  |  |9| |0|  |  |=|  |\n");
    printf("|  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_| |_| |_|  |  |_| |_|  |  |_|  |\n");
    printf("|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
    printf("|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
    printf("| Z | X | C | V | B | N | M | Q | W | E | R | T | Y | U | I | O | P | [ | ] |\n");
    printf("|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|\n\n");

    while(1) {


        // close the program
        if (keycode == 3) break;

        // restart
        else if (keycode == 18) {
            keycode = 0;
            system("cls");
            goto resetLabel;
        }

        keycode = getch();

        // update some keys
        // white
        if (keycode == 44) keycode = 113;
        else if (keycode == 46) keycode = 119;
        else if (keycode == 47) keycode = 101;

        // black
        else if (keycode == 108) keycode = 50;
        else if (keycode == 59) keycode = 51;
        makeSound(keycode, dur);
    }
}

You can also find this code on GitHub, which also includes better and organized version of this code, thanks to null8626.

The code above can be compiled using the following command:

Using gcc
$ gcc <inputFileName.c> -o piano

Now we have a working piano app:

image.png

 
Share this