Skip to content

Commit 146860b

Browse files
committed
Commit vinyl decoder source
1 parent 62132b4 commit 146860b

14 files changed

+470
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.o
2+
vinyldc
3+
*.wav
4+
tags

Makefile

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CC=gcc
2+
CFLAGS=-c -Wall -lSDL2 -lSDL2_image -lm -g
3+
LDFLAGS=-lSDL2 -lSDL2_image -lm -g
4+
SOURCES=main.c wav.c vinyl.c point.c surface.c
5+
OBJECTS=$(SOURCES:.c=.o)
6+
EXECUTABLE=vinyldc
7+
8+
all: $(SOURCES) $(EXECUTABLE)
9+
10+
$(EXECUTABLE): $(OBJECTS)
11+
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
12+
13+
.c.o:
14+
$(CC) $(CFLAGS) $< -o $@
15+
16+
clean:
17+
rm -f $(OBJECTS) $(EXECUTABLE)

README.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Description
2+
===========
3+
My go at the interesting Samsung interview task I have found on the internet recently
4+
5+
The task is desribed in the task.png (in Russian)
6+
7+
### TL;DR; & for non Russian speakers:
8+
9+
Given the image of vinyl plate write the program which decodes it into the sound.
10+
11+
12+
Compillation and running
13+
========================
14+
15+
1. install libsdl2:
16+
`apt-get install libsdl2-dev`
17+
2. compile and run:
18+
`make && ./vinyldc vinyl.png`
19+
3. play decoded file (command line vlc player is used in this example):
20+
`cvlc vinyl_decoded.wav`
21+
22+
Algorithm description
23+
=====================
24+
25+
*Mind that we are using display coordinates which are always positive and start at (0,0) in the top left screen corner*
26+
27+
1. Start in the image top center (x,y) = (Screen width / 2, 0)
28+
2. Move down by incrementing Y untill we find pixel with the value 'brighter' than certain grey shade threshold value2.1 If we have reached the center without finding any bright pixel, stop processing the image else, save the position of the first 'bright' pixel we have found into the track_start
29+
3. Rotate track_start counterclockwise and save rotation result into pnew. Whenever it is light enough, write the sample into the wav file and proceed with rotation. If pnew is not light enough, there is a couple of possibilities of us having:
30+
3.1. strayed off the track into the dark zone between the tracks (most frequent case):
31+
-- in this case we twiggle start point 2 pixels up and rotate -> this will result into rotated point to be a bit more far away form the center and might move us back on track. If it didn't and we are still on the dark spot:
32+
-- twiggle start point 2 pixels down and rotate -> this will move our pixel a bit closer to the center. If it didn't, proceed to the next case:
33+
3.2. have reached the end of the track (dark circle in the middle of the plate):
34+
-- move 30 pixels towards the plate center - if all of the pixels are dark, assume that we have reached the end
35+
of the plate and stop processing. If it is not the end, proceed to the next case:
36+
3.3. encountered a very dark bump on the track and the algoritm considered it to be 'off track' value. Add the value
37+
to the sample file as if it was light enough
38+
4. After we have reached the end of the plate, prepend the wav header with the amount of samples we have written to the beginning of the sample file.
39+

main.c

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#include <SDL2/SDL.h>
2+
#include <SDL2/SDL_image.h>
3+
#include <stdio.h>
4+
#include <string.h>
5+
#include <unistd.h>
6+
7+
#include "wav.h"
8+
#include "vinyl.h"
9+
#include "point.h"
10+
#include "surface.h"
11+
12+
#define DECODED_SOUND_FILE "vinyl_decoded.wav"
13+
#define BLACK_THRESHOLD 200
14+
#define DEGREE_STRIDE 0.1
15+
#define SCREEN_WIDTH 2000
16+
#define SCREEN_HEIGHT 2000
17+
18+
struct point surface_point_rotate(const struct point *p, double degree)
19+
{
20+
// since point is rotated around the center (0,0), we substract
21+
// half of the screen WIDTH and HEIGHT from X and Y so as to move
22+
// it to the center (image coordinates are always positive and the
23+
// (0, 0) position is in the top left corner
24+
int x_offset = SCREEN_WIDTH / 2;
25+
int y_offset = SCREEN_HEIGHT / 2;
26+
27+
struct point aligned_pt = mkpoint(p->x - x_offset, p->y - y_offset);
28+
struct point rotated_pt = point_rotate(&aligned_pt, degree);
29+
30+
return mkpoint(rotated_pt.x + x_offset, rotated_pt.y + y_offset);
31+
}
32+
33+
// NOTE: since the plate is black & white, each pixel will have R == G == B
34+
// (shades of grey). We use red pixel component but using G or B will produce
35+
// the same result
36+
37+
// crawl 30 pixels in the direction of the plate center from the point equal to
38+
// 'start', rotated 'deg' degree counterclockwise
39+
// If we do not encounter any tracks on the way, we assume that we
40+
// have reached the end of the plate (last track before large black
41+
// circle in the middle of the plate)
42+
bool looks_like_end(SDL_Surface *png_surface, struct point start, double deg)
43+
{
44+
const int dead_end_pixel_count = 30;
45+
int black_pixels = 0;
46+
struct point pt;
47+
struct pixel pix;
48+
while (black_pixels < dead_end_pixel_count) {
49+
pt = surface_point_rotate(&start, deg);
50+
pix = get_pixel(png_surface, &pt);
51+
if (pix.r > BLACK_THRESHOLD) {
52+
break;
53+
}
54+
start.y++;
55+
black_pixels++;
56+
}
57+
return black_pixels == dead_end_pixel_count;
58+
}
59+
60+
// crawl from the image top center (SCREEN_WIDTH / 2, 0), down along
61+
// the Y axis till we spot the first white track coloured lighter than
62+
// BLACK_THRESHOLD
63+
struct point find_start(SDL_Surface* png_surface)
64+
{
65+
int x_start, y_start;
66+
struct point start;
67+
int i;
68+
for (i = 0; i < SCREEN_HEIGHT / 2; i++) {
69+
struct point pt = mkpoint(SCREEN_WIDTH / 2, i);
70+
struct pixel pix = get_pixel(png_surface, &pt);
71+
if (pix.r > BLACK_THRESHOLD) {
72+
x_start = SCREEN_WIDTH / 2;
73+
y_start = i;
74+
start = mkpoint(x_start, y_start);
75+
76+
put_red_pixel(png_surface, &start);
77+
break;
78+
}
79+
}
80+
return start;
81+
}
82+
83+
void vinyl_decode(SDL_Window* sdl_window, SDL_Surface* screen_surface, SDL_Surface* png_surface)
84+
{
85+
bool quit = false;
86+
int samples_written = 0;
87+
struct wav_hdr hdr;
88+
SDL_Event e;
89+
FILE *decoded_sound = fopen(DECODED_SOUND_FILE, "w");
90+
struct point start = find_start(png_surface);
91+
92+
if (decoded_sound == NULL) {
93+
perror("Failed to write decoded data");
94+
return;
95+
}
96+
97+
// save the place for wav header, which we will use to write
98+
// the actual header
99+
// We do not write it in advance since we have to know
100+
// how much samples we have read to put that count into wav_hdr
101+
fseek(decoded_sound, sizeof(hdr), SEEK_SET);
102+
103+
while (!quit) {
104+
while (SDL_PollEvent(&e) != 0) {
105+
if (e.type == SDL_QUIT) {
106+
quit = true;
107+
}
108+
}
109+
110+
SDL_BlitSurface(png_surface, NULL, screen_surface, NULL);
111+
112+
double deg;
113+
for (deg = 0; deg > -360.0; deg -= DEGREE_STRIDE) {
114+
struct point pnew;
115+
struct pixel pix;
116+
117+
pnew = surface_point_rotate(&start, deg);
118+
pix = get_pixel(png_surface, &pnew);
119+
// check if we are still on the track
120+
if (pix.r > BLACK_THRESHOLD) {
121+
fwrite(&pix.r, sizeof(pix.r), 1, decoded_sound);
122+
samples_written++;
123+
put_red_pixel(png_surface, &pnew);
124+
continue;
125+
126+
}
127+
128+
// probe pixels above our point
129+
struct point yinc = mkpoint(start.x, start.y + 2);
130+
pnew = surface_point_rotate(&yinc, deg);
131+
pix = get_pixel(png_surface, &pnew);
132+
if (pix.r > BLACK_THRESHOLD) {
133+
start = yinc;
134+
fwrite(&pix.r, sizeof(pix.r), 1, decoded_sound);
135+
samples_written++;
136+
put_red_pixel(png_surface, &pnew);
137+
continue;
138+
}
139+
140+
141+
// probe pixels below our point
142+
struct point ydec = mkpoint(start.x, start.y - 2);
143+
pnew = surface_point_rotate(&ydec, deg);
144+
pix = get_pixel(png_surface, &pnew);
145+
if (pix.r > BLACK_THRESHOLD) {
146+
start = ydec;
147+
fwrite(&pix.r, sizeof(pix.r), 1, decoded_sound);
148+
samples_written++;
149+
put_red_pixel(png_surface, &pnew);
150+
continue;
151+
}
152+
153+
// awkward case when we are on the dark spot and pixels
154+
// above and below are also dark - this means that we have
155+
// either reached the end of the plate...
156+
if (looks_like_end(png_surface, start, deg)) {
157+
quit = true;
158+
break;
159+
}
160+
161+
// ...or we have stumbled upon the 'bump' which is lower than
162+
// BLACK_THRESHOLD value but is still on the track
163+
pnew = surface_point_rotate(&start, deg);
164+
pix = get_pixel(png_surface, &pnew);
165+
fwrite(&pix.r, sizeof(pix.r), 1, decoded_sound);
166+
put_red_pixel(png_surface, &pnew);
167+
samples_written++;
168+
}
169+
170+
SDL_UpdateWindowSurface(sdl_window);
171+
}
172+
173+
// replace placeholder wav_hdr struct in the beginning of file with the actual one
174+
hdr = mk_wav_hdr(samples_written);
175+
rewind(decoded_sound);
176+
fwrite(&hdr, sizeof(hdr), 1, decoded_sound);
177+
fclose(decoded_sound);
178+
printf("wrote samples: %d\n", samples_written);
179+
usleep(1000000);
180+
}
181+
int main(int argc, char* args[])
182+
{
183+
SDL_Window* sdl_window = NULL;
184+
//The surface contained by the window
185+
SDL_Surface* screen_surface = NULL;
186+
SDL_Surface* png_surface = NULL;
187+
const char *plate_name = args[1];
188+
189+
if (argc != 2) {
190+
printf("Usage:\n"
191+
"%s <vinyl-image-to-decode>\n", args[0]);
192+
goto fail;
193+
}
194+
195+
//Start up SDL and create window
196+
if (!vinyl_init()) {
197+
goto fail;
198+
}
199+
200+
sdl_window = SDL_CreateWindow( "SDL Tutorial", 500, 500, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
201+
if (sdl_window == NULL ) {
202+
printf( "Failed to create SDL Window: %s\n", SDL_GetError() );
203+
goto window_creation_fail;
204+
}
205+
206+
screen_surface = SDL_GetWindowSurface(sdl_window);
207+
if (screen_surface == NULL ) {
208+
printf("Failed to get window surface: %s\n", SDL_GetError());
209+
goto surface_creation_fail;
210+
}
211+
212+
png_surface = load_surface(screen_surface, plate_name);
213+
SDL_FreeSurface(screen_surface);
214+
if (png_surface == NULL) {
215+
printf("Failed to load PNG image: %s\n", SDL_GetError());
216+
goto surface_creation_fail;
217+
}
218+
219+
vinyl_decode(sdl_window, screen_surface, png_surface);
220+
221+
SDL_FreeSurface(png_surface);
222+
surface_creation_fail:
223+
SDL_DestroyWindow(sdl_window);
224+
window_creation_fail:
225+
vinyl_exit();
226+
fail:
227+
return 0;
228+
}
229+

point.c

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <math.h>
2+
3+
#include "point.h"
4+
5+
struct point mkpoint(int x, int y)
6+
{
7+
struct point ret;
8+
ret.x = x;
9+
ret.y = y;
10+
return ret;
11+
}
12+
13+
static inline double deg_to_rad(double deg)
14+
{
15+
return deg * M_PI / 180.0;
16+
}
17+
18+
struct point point_rotate(const struct point *p, double degree)
19+
{
20+
double rad = deg_to_rad(degree);
21+
int x_new = p->x * cos(rad) - p->y * sin(rad);
22+
int y_new = p->x * sin(rad) + p->y * cos(rad);
23+
24+
return mkpoint(x_new, y_new);
25+
}

point.h

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
struct point {
2+
int x;
3+
int y;
4+
};
5+
6+
struct point mkpoint(int x, int y);
7+
struct point point_rotate(const struct point *p, double degree);
8+

surface.c

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <SDL2/SDL.h>
2+
#include <SDL2/SDL_image.h>
3+
4+
#include "surface.h"
5+
#include "point.h"
6+
7+
SDL_Surface* load_surface(SDL_Surface* screen_surface, const char* path)
8+
{
9+
//The final optimized image
10+
SDL_Surface* optimizedSurface = NULL;
11+
12+
//Load image at specified path
13+
SDL_Surface* loadedSurface = IMG_Load(path);
14+
if (loadedSurface == NULL) {
15+
printf("Unable to load image %s: %s\n", path, IMG_GetError());
16+
goto out;
17+
}
18+
19+
//Convert surface to screen format
20+
optimizedSurface = SDL_ConvertSurface(loadedSurface, screen_surface->format, 0);
21+
if (optimizedSurface == NULL) {
22+
printf("Unable to optimize image %s: %s\n", path, SDL_GetError());
23+
}
24+
25+
out:
26+
return optimizedSurface;
27+
}
28+
29+
30+
void put_red_pixel(SDL_Surface *surface, const struct point *pt)
31+
{
32+
Uint32* pixels = (Uint32*)surface->pixels;
33+
pixels += pt->y * surface->w + pt->x;
34+
*pixels = SDL_MapRGB(surface->format, 255, 0, 0);
35+
}
36+
37+
struct pixel get_pixel(SDL_Surface *surface, const struct point *pt)
38+
{
39+
struct pixel pix;
40+
41+
Uint32* pixels = (Uint32*)surface->pixels;
42+
pixels += pt->y * surface->w + pt->x;
43+
Uint8* pixel_bytes = (Uint8*) pixels;
44+
45+
pix.r = pixel_bytes[0];
46+
pix.g = pixel_bytes[1];
47+
pix.b = pixel_bytes[2];
48+
return pix;
49+
}

surface.h

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
struct SDL_Surface;
2+
struct point;
3+
4+
struct pixel {
5+
Uint8 r;
6+
Uint8 g;
7+
Uint8 b;
8+
};
9+
10+
SDL_Surface* load_surface(SDL_Surface *screen, const char* path);
11+
void put_red_pixel(SDL_Surface *surface, const struct point *pt);
12+
struct pixel get_pixel(SDL_Surface *surface, const struct point *pt);

task.png

111 KB
Loading

0 commit comments

Comments
 (0)