1 module midi2.func.mcode7;
2 
3 /**
4  * midi2 - MIDI 2.0 implementation.
5  *
6  * midi2.func.mcode7:
7  *
8  * Implements Mcoded7 algorithms
9  *
10  * This algorithm is used for MIDI CI, but also can be used to encode things like binary data streams sent through
11  * MIDI SysEx commands.
12  *
13  * From the following input:
14  * AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg
15  *
16  * Generates the following output:
17  * 0ABCDEFG 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg
18  */
19 
20 /**
21  * Mcoded7 coder status return codes.
22  */
23 enum MCoded7Status {
24 	Finished,
25 	OutputTooSmall,
26 	BuffersUnaligned
27 }
28 version (midi2_betterc) {
29 
30 } else {
31 	/**
32 	 * MCoded7 encoder function.
33 	 * 
34 	 * For algorithm simplicity, it needs a pre-allocated output, and aligned buffers.
35 	 */
36 	MCoded7Status encodeStream(const(ubyte)[] input, ubyte[] output) @safe pure nothrow {
37 		void encodeChunk(const(ubyte)[] currIn, ubyte[] currOut) @nogc @safe pure nothrow {
38 			for (int i ; i < 7 ; i++) {
39 				currOut[i + 1] = currIn[i] & 0x7F;
40 				currOut[0] |= (currIn[i] & 0x80)>>(i + 1);
41 			}
42 		}
43 		//Assert that the buffers are unaligned
44 		if (output.length % 8 != 0 && input.length % 7) return MCoded7Status.BuffersUnaligned;
45 		//Assert that there will be enough space in the output buffer
46 		if (output.length < input.length + input.length / 7) return MCoded7Status.OutputTooSmall;
47 		for (int i, j ; i < input.length ; i+=7, j+=8) {
48 			encodeChunk(input[i..i+7], output[j..j+8]);
49 		}
50 		return MCoded7Status.Finished;
51 	}
52 	/**
53 	 * MCoded7 decoder function.
54 	 *
55 	 * For algorithm simplicity, it needs a pre-allocated output, and aligned buffers.
56 	 */
57 	MCoded7Status decodeStream(const(ubyte)[] input, ubyte[] output) @safe pure nothrow {
58 		void decodeChunk(const(ubyte)[] currIn, ubyte[] currOut) @nogc @safe pure nothrow {
59 			for (int i ; i < 7 ; i++) {
60 				currOut[i] = cast(ubyte)((currIn[0] << (1 + i) & 0x80) | currIn[i + 1]);
61 			}
62 		}
63 		//Assert that the buffers are unaligned
64 		if (output.length % 7 != 0 && input.length % 8) return MCoded7Status.BuffersUnaligned;
65 		//Assert that there will be enough space in the output buffer
66 		if (output.length < input.length - input.length / 8) return MCoded7Status.OutputTooSmall;
67 		for (int i, j ; i < input.length ; i+=8, j+=7) {
68 			decodeChunk(input[i..i+8], output[j..j+7]);
69 		}
70 		return MCoded7Status.Finished;
71 	}
72 	unittest {
73 		import std.conv : to;
74 		const(char)[] input = "this is a test";
75 		char[] output;
76 		ubyte[] encoded;
77 		encoded.length = 16;
78 		output.length = 14;
79 		MCoded7Status result = encodeStream(cast(const(ubyte)[])input, encoded);
80 		assert (result == MCoded7Status.Finished, to!string(result));
81 		result = decodeStream(cast(const(ubyte)[])encoded, cast(ubyte[])output);
82 		assert (result == MCoded7Status.Finished, to!string(result));
83 		assert (input == output, output);
84 	}
85 }
86 
87 /+/**
88  * Mcoded7 encoder.
89  *
90  * Uses either a classical length-pointer pair for nogc targets, or D's own dynamic arrays for gc targets.
91  */
92 struct MCoded7Encoder {
93 	private size_t		counter;	///The amount of encoded data.
94 	private size_t		outCount;	///The amount of outputted data.
95 	private ubyte[8]	currOut;	///The currently outputted data.
96 	private ubyte[7]	currIn;		///The currently inputted data.
97 	private ubyte		flags;		///Status flags. Bit 7 is set if finalization is complete
98 	version (midi2_nogc) {
99 		private size_t			inSize;	///The remaining data in the input stream
100 		private const(ubyte)*	input;	///The pointer to the current byte in the input stream
101 		private size_t			outSize;///The remaining data in the output stream
102 		private ubyte*			output;	///The pointer to the current first byte in the output stream
103 	} else {
104 		private const(ubyte)[]	input;	///The input stream
105 		private ubyte[]			output;	///The output stream
106 		private size_t			inPos;	///Input position
107 		private size_t			outPos;	///Output position
108 	}
109 	version (midi2_nogc) {
110 		@nogc @safe pure nothrow {
111 			/**
112 			 * Creates an encoder with the supplied starting streams
113 			 */
114 			this(size_t inSize, const(ubyte)* input, size_t outSize, ubyte output) {
115 				this.inSize = inSize;
116 				this.input = input;
117 				this.outSize = outSize;
118 				this.output = output;
119 			}
120 			/**
121 			 * Encodes all possible data on the input stream without needing to pad out the end
122 			 */
123 			MCoded7Status encode() @trusted {
124 				if (outCount % 8) {		//empty output if needed
125 					if (emptyOutputChunk())
126 						return MCoded7Status.NeedsMoreOutput;
127 				}
128 				if (!(flags & 0x80)) {
129 					while (inSize) {
130 						if (fillInputChunk())
131 							return MCoded7Status.AllInputConsumed;
132 						encodeChunk();
133 						if (emptyOutputChunk())
134 							return MCoded7Status.NeedsMoreOutput;
135 					}
136 					return MCoded7Status.AllInputConsumed;
137 				} else return MCoded7Status.AlreadyFinalized;
138 			}
139 			/**
140 			 * Finalizes the stream once no more output is needed to be put onto the stream.
141 			 * Consumes the remaining data on the input if any, then pads the end if needed.
142 			 */
143 			MCoded7Status finalize() @trusted {
144 				if (flags & 0x80) return MCoded7Status.AlreadyFinalized;
145 				//Encode everything if it still haven't been
146 				const MCoded7Status state = encode();
147 				//At this point, the output must have enough space to accomodate an extra chunk. If not, then error 
148 				//out with the NeedsMoreOutput code.
149 				if (state == MCoded7Status.NeedsMoreOutput && outSize < 7)
150 					return MCoded7Status.NeedsMoreOutput;
151 				currOut = [0,0,0,0,0,0,0,0];
152 				encodeChunk();
153 				if (emptyOutputChunk())
154 					return MCoded7Status.NeedsMoreOutput;
155 				flags |= 0x80;
156 				return MCoded7Status.Finished;
157 			}
158 			/**
159 			 * Sets the input stream.
160 			 */
161 			void setInputStream(const(ubyte)* input, size_t inSize) {
162 				this.input = input;
163 				this.inSize = inSize;
164 			}
165 			/**
166 			 * Sets the output stream.
167 			 */
168 			void setOutputStream(ubyte* output, size_t outSize) {
169 				this.output = output;
170 				this.outSize = outSize;
171 			}
172 			/**
173 			 * Fills the current input chunk from the input stream and raises the counter by the amount.
174 			 * Returns 0 if the chunk is completed. Returns 1-6 if the input chunk isn't complete.
175 			 */
176 			protected int fillInputChunk() @system {
177 				if (!inSize) return -1;
178 				do {
179 					if (!inSize) return counter % 7;
180 					currIn[counter % 7] = *input;
181 					inSize--;
182 					input++;
183 					counter++;
184 				} while (counter % 7);
185 				return 0;
186 			}
187 			/**
188 			 * Empties the current output chunk to the output stream, and raises the output counter by the amount.
189 			 * Returns 0 if the chunk is completed. Returns 1-7 if the chunk isn't complete.
190 			 */
191 			protected int emptyOutputChunk() @system {
192 				if (!outSize) return -1;
193 				do {
194 					if (!outSize) return outCount % 8;
195 					*output = currOut[outCount % 8];
196 					currOut[outCount % 8] = 0x0;
197 					outSize--;
198 					output++;
199 					outCount++;
200 				} while (outCount % 8);
201 				return 0;
202 			}
203 		}
204 	} else {
205 		@safe pure nothrow {
206 			/**
207 			 * Creates an encoder with the supplied starting streams
208 			 */
209 			this(const(ubyte)[] input, ubyte[] output) {
210 				this.input = input;
211 				this.output = output;
212 			}
213 			/**
214 			 * Encodes all possible data on the input stream without needing to pad out the end
215 			 */
216 			MCoded7Status encode() {
217 				if (outCount % 8) {		//empty output if needed
218 					if (emptyOutputChunk())
219 						return MCoded7Status.NeedsMoreOutput;
220 				}
221 				if (!(flags & 0x80)) {
222 					if (fillInputChunk())
223 						return MCoded7Status.AllInputConsumed;
224 					encodeChunk();
225 					if (emptyOutputChunk())
226 						return MCoded7Status.NeedsMoreOutput;
227 					return MCoded7Status.AllInputConsumed;
228 				} else return MCoded7Status.AlreadyFinalized;
229 			}
230 			/**
231 			 * Finalizes the stream once no more output is needed to be put onto the stream.
232 			 * Consumes the remaining data on the input if any, then pads the end if needed.
233 			 */
234 			MCoded7Status finalize() {
235 				if (flags & 0x80) return MCoded7Status.AlreadyFinalized;
236 				//Encode everything if it still haven't been
237 				const MCoded7Status state = encode();
238 				//At this point, the output must have enough space to accomodate an extra chunk. If not, then error 
239 				//out with the NeedsMoreOutput code.
240 				if (state == MCoded7Status.NeedsMoreOutput && output.length - outPos < 7)
241 					return MCoded7Status.NeedsMoreOutput;
242 				currOut = [0,0,0,0,0,0,0,0];
243 				encodeChunk();
244 				emptyOutputChunk();
245 				flags |= 0x80;
246 				return MCoded7Status.Finished;
247 			}
248 			/**
249 			 * Sets the input stream.
250 			 */
251 			void setInputStream(const(ubyte)[] input) {
252 				this.input = input;
253 				inPos = 0;
254 			}
255 			/**
256 			 * Sets the output stream.
257 			 */
258 			void setOutputStream(ubyte[] output) {
259 				this.output = output;
260 				outPos = 0;
261 			}
262 			/**
263 			 * Fills the current input chunk from the input stream and raises the counter by the amount.
264 			 * Returns 0 if the chunk is completed. Returns 1-6 if the input chunk isn't complete. Returns -1 if
265 			 * there's no input left
266 			 */
267 			protected int fillInputChunk() {
268 				if (input.length == inPos) return -1;
269 				do {
270 					if (input.length == inPos) return counter % 7;
271 					currIn[counter % 7] = input[inPos];
272 					inPos++;
273 					counter++;
274 				} while (counter % 7);
275 				return 0;
276 			}
277 			/**
278 			 * Empties the current output chunk to the output stream, and raises the output counter by the amount.
279 			 * Returns 0 if the chunk is completed. Returns 1-7 if the chunk isn't complete.
280 			 */
281 			protected int emptyOutputChunk() {
282 				do {
283 					if (output.length == outPos) return outCount % 8;
284 					output[outPos] = currOut[outCount % 8];
285 					
286 					outPos++;
287 					outCount++;
288 				} while (outCount % 8);
289 				return 0;
290 			}
291 		}
292 	}
293 	@nogc @safe pure nothrow {
294 		/**
295 		 * Encodes the current chunk.
296 		 */
297 		protected void encodeChunk() {
298 			for (int i ; i < 7 ; i++) {
299 				currOut[i + 1] = currIn[i] & 0x7F;
300 				currOut[0] |= (currIn[i] & 0x80)>>(i + 1);
301 			}
302 		}
303 	}
304 }
305 /**
306  * Mcoded7 decode.
307  *
308  * Uses either a classical length-pointer pair for nogc targets, or D's own dynamic arrays for gc targets.
309  */
310 struct MCoded7Decoder {
311 	private size_t		counter;	///The amount of encoded data.
312 	private size_t		outCount;	///The amount of outputted data.
313 	private ubyte[8]	currIn;		///The currently outputted data.
314 	private ubyte[7]	currOut;	///The currently inputted data.
315 	private ubyte		flags;		///Status flags. Bit 7 is set if finalization is complete
316 	version (midi2_nogc) {
317 		private size_t			inSize;	///The remaining data in the input stream
318 		private const(ubyte)*	input;	///The pointer to the current byte in the input stream
319 		private size_t			outSize;///The remaining data in the output stream
320 		private ubyte*			output;	///The pointer to the current first byte in the output stream
321 	} else {
322 		private const(ubyte)[]	input;	///The input stream
323 		private ubyte[]			output;	///The output stream
324 		private size_t			inPos;	///Input position
325 		private size_t			outPos;	///Output position
326 	}
327 	version (midi2_nogc) {
328 		@nogc @safe pure nothrow{
329 			/**
330 			 * Creates a standard decoder with the supplied streams.
331 			 */
332 			this(size_t inSize, const(ubyte)* input, size_t outSize, ubyte* output) {
333 				this.inSize = inSize;
334 				this.Input = Input;
335 				this.outSize = outSize;
336 				this.output = output;
337 			}
338 			/**
339 			 * Encodes all possible data on the input stream without needing to pad out the end
340 			 */
341 			MCoded7Status decode() @trusted {
342 				if (outCount % 8) {		//empty output if needed
343 					if (emptyOutputChunk())
344 						return MCoded7Status.NeedsMoreOutput;
345 				}
346 				if (!(flags & 0x80)) {
347 					while (inSize) {
348 						if (fillInputChunk())
349 							return MCoded7Status.AllInputConsumed;
350 						decodeChunk();
351 						if (emptyOutputChunk())
352 							return MCoded7Status.NeedsMoreOutput;
353 					}
354 					return MCoded7Status.AllInputConsumed;
355 				} else return MCoded7Status.AlreadyFinalized;
356 			}
357 			/**
358 			 * Finalizes the stream once no more output is needed to be put onto the stream.
359 			 * Consumes the remaining data on the input if any, then pads the end if needed.
360 			 */
361 			MCoded7Status finalize() @trusted {
362 				if (!flags & 0x80) return MCoded7Status.AlreadyFinalized;
363 				//Encode everything if it still haven't been
364 				MCoded7Status state = decode();
365 				//At this point, the output must have enough space to accomodate an extra chunk. If not, then error 
366 				//out with the NeedsMoreOutput code.
367 				if (state == MCoded7Status.NeedsMoreOutput || outSize < 8)
368 					return state;
369 				currOut = [0,0,0,0,0,0,0];
370 				encodeChunk();
371 				if (emptyOutputChunk())
372 					return MCoded7Status.NeedsMoreOutput;
373 				flags |= 0x80;
374 				return MCoded7Status.Finished;
375 			}
376 			/**
377 			 * Sets the input stream.
378 			 */
379 			void setInputStream(const(ubyte)* input, size_t inSize) {
380 				this.input = input;
381 				this.inSize = inSize;
382 			}
383 			/**
384 			 * Sets the output stream.
385 			 */
386 			void setOutputStream(ubyte* output, size_t outSize) {
387 				this.output = output;
388 				this.outSize = outSize;
389 			}
390 			/**
391 			 * Fills input chunk.
392 			 * Returns the amount that is missing from the input, -1 if there's none on the input stream, 0 if 
393 			 * everything went alright.
394 			 */
395 			protected int fillInputChunk() @system {
396 				if (!inSize) return -1;
397 				do {
398 					if (!inSize) return counter % 8;
399 					currIn[counter % 8] = *input;
400 					input++;
401 					inSize--;
402 					counter++;
403 				} while (counter % 8);
404 				return 0;
405 			}
406 			/**
407 			 * Empties output chunk.
408 			 * Returns the amough that is missing from the input, -1 if there's none on the output stream, 0 
409 			 * if everything went alright
410 			 */
411 			protected int emptyOutputChunk() @system {
412 				if (!outSize) return -1;
413 				do {
414 					if (!outSize) return outCount % 7;
415 					*output = currOut[counter % 7];
416 					
417 					output++;
418 					outSize--;
419 					outCount++;
420 				} while (outCount % 7);
421 				return 0;
422 			}
423 		}
424 	} else {
425 		@nogc @safe pure nothrow {
426 			/**
427 			 * Creates a standard decoder with the supplied streams.
428 			 */
429 			this (const(ubyte)[] input, ubyte[] output) {
430 				this.input = input;
431 				this.output = output;
432 			}
433 			/**
434 			 * Encodes all possible data on the input stream without needing to pad out the end
435 			 */
436 			MCoded7Status decode() @trusted {
437 				if (outCount % 8) {		//empty output if needed
438 					if (emptyOutputChunk())
439 						return MCoded7Status.NeedsMoreOutput;
440 				}
441 				if (!(flags & 0x80)) {
442 					while (input.length < inPos) {
443 						if (fillInputChunk())
444 							return MCoded7Status.AllInputConsumed;
445 						decodeChunk();
446 						if (emptyOutputChunk())
447 							return MCoded7Status.NeedsMoreOutput;
448 					}
449 					return MCoded7Status.AllInputConsumed;
450 				} else return MCoded7Status.AlreadyFinalized;
451 			}
452 			/**
453 			 * Finalizes the stream once no more output is needed to be put onto the stream.
454 			 * Consumes the remaining data on the input if any, then pads the end if needed.
455 			 */
456 			MCoded7Status finalize() @trusted {
457 				if (!flags & 0x80) return MCoded7Status.AlreadyFinalized;
458 				MCoded7Status state = decode();
459 				if (state == MCoded7Status.NeedsMoreOutput)
460 					return state;
461 				currOut = [0,0,0,0,0,0,0];
462 				decodeChunk();
463 				if (emptyOutputChunk())
464 					return MCoded7Status.NeedsMoreOutput;
465 				flags |= 0x80;
466 				return MCoded7Status.Finished;
467 			
468 			}
469 			/**
470 			 * Fills the current input chunk from the input stream and raises the counter by the amount.
471 			 * Returns 0 if the chunk is completed. Returns 1-6 if the input chunk isn't complete. Returns -1 if
472 			 * there's no input left
473 			 */
474 			protected int fillInputChunk() {
475 				if (input.length == inPos) return -1;
476 				do {
477 					if (input.length == inPos) return counter % 8;
478 					currIn[counter % 8] = input[inPos];
479 					inPos++;
480 					counter++;
481 				} while (counter % 8);
482 				return 0;
483 			}
484 			/**
485 			 * Empties the current output chunk to the output stream, and raises the output counter by the amount.
486 			 * Returns 0 if the chunk is completed. Returns 1-7 if the chunk isn't complete.
487 			 */
488 			protected int emptyOutputChunk() {
489 				if (output.length == outPos) return -1;
490 				do {
491 					if (output.length == outPos) return outCount % 7;
492 					output[outPos] = currOut[outCount % 7];
493 					
494 					outPos++;
495 					outCount++;
496 				} while (outCount % 7);
497 				return 0;
498 			}
499 			/**
500 			 * Sets the input stream.
501 			 */
502 			void setInputStream(const(ubyte)[] input) {
503 				this.input = input;
504 				inPos = 0;
505 			}
506 			/**
507 			 * Sets the output stream.
508 			 */
509 			void setOutputStream(ubyte[] output) {
510 				this.output = output;
511 				outPos = 0;
512 			}
513 		}
514 	}
515 	@nogc @safe pure nothrow {
516 		/**
517 		 * Decodes an Mcoded7 chunk.
518 		 */
519 		protected void decodeChunk() {
520 			for (int i ; i < 7 ; i++) {
521 				currOut[i] = cast(ubyte)((currIn[0] << (1 + i) & 0x80) | currIn[i + 1]);
522 			}
523 		}
524 
525 	}
526 }
527 
528 
529 version (midi2_nogc) {
530 	@nogc nothrow unittest {
531 
532 	}
533 } else {
534 	unittest {
535 		import std.stdio;
536 		import std.conv : to;
537 		const(char)[] input = "this is a test!";
538 		char[] output;
539 		ubyte[] encoded;
540 		encoded.length = 16;
541 		output.length = input.length;
542 		MCoded7Encoder encoder = MCoded7Encoder(cast(const(ubyte)[])input, encoded);
543 		//encoder.encode;
544 		MCoded7Status status = encoder.finalize;
545 		assert(status == MCoded7Status.Finished, to!string(status));
546 		writeln(encoded);
547 		MCoded7Decoder decoder = MCoded7Decoder(cast(const(ubyte)[])encoded, cast(ubyte[])output);
548 		status = decoder.finalize;
549 		assert(status == MCoded7Status.Finished);
550 		assert(input == output, output);
551 	}
552 }+/