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 }+/