-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBlackLine.java
309 lines (250 loc) · 9.02 KB
/
BlackLine.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
* Name: Rynel Luo, Vidula Kopli
* PennKey: rynelluo, vkopli
* Recitation: 215, 216
*
* Description: A black line from the sheet music image
*/
public class BlackLine implements Line {
private static int[][] image; //2D array representation of music image
private static int rows, cols;
private static int grayThreshold;
private int yStart, yEnd;
private int currCol; //current column of line being looked at
private int staffIdx;
private int dyUp, dyDown; //distance to blank lines above and below
private int startCol = Integer.MIN_VALUE; //optional to change in program
private int endCol = Integer.MAX_VALUE; //optional to change in program
/**
* Saves sheet music image into the line classes
* @param 2D array of sheet music image, int value for grey color
*/
public static void initialize(int[][] insertedImage, int GRAY_THRESHOLD) {
image = insertedImage;
rows = image.length;
cols = image[0].length;
grayThreshold = GRAY_THRESHOLD;
}
/**
* Constructor to make a BlackLine object
* @param where BlackLine starts and ends
*/
public BlackLine(int yStart, int yEnd) {
if (image == null) {
throw new RuntimeException("BlackLine class not yet initialized");
}
this.yStart = yStart;
this.yEnd = yEnd;
}
/**
* Sees if there is another readable column in the image's row (line)
* @return if the image has another readable column
*/
public boolean hasNext() {
return currCol < (cols - dyUp / 2 + 1) &&
currCol < (cols - dyDown / 2 + 1) &&
currCol <= endCol;
}
/**
* Moves on to the next column in the image (line)
*/
public void next() {
currCol++;
}
/**
* Makes quarter note at current position (currCol, y)
*/
public Note makeQuarterNote() {
int y = (yStart + yEnd) / 2;
Note newNote = new QuarterNote(currCol, y, staffIdx);
return newNote;
}
/**
* Makes half note at current position (currCol, y)
*/
public Note makeHalfNote() {
int y = (yStart + yEnd) / 2;
Note newNote = new HalfNote(currCol, y, staffIdx);
return newNote;
}
/**
* Checks for a black "blob" (quarter note) on the black line at currCol
* assumes currCol is set to valid index
* @return whether there is a quarter note on the line
*/
public boolean checkQuarterNote() {
//IMPORTANT: up in image array corresponds with down in actual image
checkErrors();
boolean U, D, UL, UR, DL, DR;
boolean isNote = true;
for (int i = 1; i <= (dyUp / 2); i++) {
U = image[yStart - i][currCol] < grayThreshold;
UL = image[yStart - i][currCol - i] < grayThreshold;
UR = image[yStart - i][currCol + i] < grayThreshold;
isNote &= U && UL && UR;
}
for (int i = 1; i <= (dyDown / 2); i++) {
D = image[yEnd + i][currCol] < grayThreshold;
DL = image[yEnd + i][currCol - i] < grayThreshold;
DR = image[yEnd + i][currCol + i] < grayThreshold;
isNote &= D && DL && DR;
}
return isNote;
}
/**
* Checks for a half note on the black line at currCol
* assumes currCol is set to valid index
* @return whether there is a half note on the line
*/
public boolean checkHalfNote() {
//IMPORTANT: up in image array corresponds with down in actual image
checkErrors();
boolean U, D, UL, UR, DL, DR;
boolean isNote = true;
//check immediate surrounding pixels of note
U = image[yStart - 1][currCol] < grayThreshold;
UL = image[yStart - 1][currCol - 1] < grayThreshold;
UR = image[yStart - 1][currCol + 1] < grayThreshold;
D = image[yEnd + 1][currCol] < grayThreshold;
DL = image[yEnd + 1][currCol - 1] < grayThreshold;
DR = image[yEnd + 1][currCol + 1] < grayThreshold;
//true if all are blank
isNote &= !(U && UL && UR && D && DL && DR);
//check if pair of further upper left and lower right pixels are black
//(at least 2 in a row)
int dy;
if (dyUp < dyDown) {
dy = dyUp;
}
else {
dy = dyDown;
}
boolean furtherPixels = false;
boolean prevFurtherPixels = false;
for (int i = 2; i <= dy / 2; i++) {
UL = image[yStart - i][currCol - i] < grayThreshold;
DR = image[yEnd + i][currCol + i] < grayThreshold;
furtherPixels = UL && DR;
if (furtherPixels && prevFurtherPixels) {
break;
}
prevFurtherPixels = furtherPixels;
}
//true if at least two pairs of further pixels in a row are black
//and immediately surrounding pixels are blank
isNote &= furtherPixels && prevFurtherPixels;
return isNote;
}
/**
* Move currCol enough to the right that checkNote can check to the left
* @param int distance between the line and the line above it
*/
public void setDyUp(int dyUp) {
this.dyUp = dyUp;
if (currCol < dyUp) {
currCol = dyUp;
}
}
/**
* Move currCol enough to the right that checkNote can check to the left
* @param int distance between the line and the line below it
*/
public void setDyDown(int dyDown) {
this.dyDown = dyDown;
if (currCol < dyDown) {
currCol = dyDown;
}
}
/**
* Set the note that the line can play
* @param int which staff it is
*/
public void setStaffIdx(int staffIdx) {
this.staffIdx = staffIdx;
}
/**
* Get where the line starts
* @return where the line starts
*/
public int getYStart() {
return yStart;
}
/**
* Get where the line ends
* @return where the line ends
*/
public int getYEnd() {
return yEnd;
}
/**
* Get the y position midpoint of the line
* @return the y position midpoint of the line
*/
public int getY() {
return (yStart + yEnd) / 2;
}
/**
* Set currCol/startCol and endCol
* @param the number of columns
*/
public void setXlim(int numCols) {
startCol = numCols;
currCol = numCols;
endCol = cols - 1 - numCols;
}
/**
* Optional function to further restrict columns that are searched
* @param start and end columns
*/
public void setXlim(int startCol, int endCol) {
this.startCol = startCol;
this.endCol = endCol;
//shift currCol to the right if wanting to exclude something
if (startCol > currCol) {
currCol = startCol;
}
}
/**
* Checks that the values of currCol and dyUp/dyDown have been set, and
* whether the given line is too close to the upper/lower image edge to
* be able to check for a note
* If not, throws an error
*/
private void checkErrors() {
if (currCol == 0) {
throw new RuntimeException("ERROR: currCol not initialized");
}
if (dyUp == 0 || dyDown == 0) {
throw new RuntimeException("ERROR: dy not set for blackLine");
}
if ((yStart - dyUp) < 0 || (yEnd + dyDown) >= rows) {
throw new IllegalArgumentException("ERROR: staff lines too close" +
"to upper/lower image edge");
}
}
/**
* Visualize the black lines for user
*/
public void draw() {
PennDraw.setPenColor(PennDraw.BLACK);
int x1, x2;
if (startCol == Integer.MIN_VALUE || endCol == Integer.MAX_VALUE) {
x1 = startCol;
x2 = endCol;
}
else {
x1 = 0;
x2 = cols;
}
if (yStart == yEnd) {
PennDraw.line(x1, yStart, x2, yStart);
}
else {
double xCenter = (x2 - x1) / 2.0;
double yCenter = (yStart + yEnd) / 2.0;
double halfWidth = (x2 + x1) / 2.0;
double halfHeight = yEnd - yStart;
PennDraw.filledRectangle(xCenter, yCenter, halfWidth, halfHeight);
}
}
}