/[projet1]/public/pc/tools/osdk/main/Ym2Mym/sources/Ym2Mym.cpp
Defence Force logotype

Contents of /public/pc/tools/osdk/main/Ym2Mym/sources/Ym2Mym.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1200 - (show annotations)
Sat Sep 20 16:15:04 2014 UTC (4 years, 11 months ago) by dbug
File size: 15008 byte(s)
replaced ssize_t by ptrdiff_t
1 /*
2 ym2mym.cpp
3
4 Converts _unpacked_ YM tunes to a format better suitable for
5 MSX1. Supports YM2 and YM3 types well plus YM5 somehow.
6
7 30.1.2000 Marq/Lieves!Tuore & Fit (marq@iki.fi)
8
9 3.2.2000 - Added a rude YM5 loader. Skips most of the header.
10
11 Output format:
12
13 Rows in the tune, 16 bits (lobyte first)
14 For each register, 0 - fragment contains only unchanged data
15 1 - fragment contains packed data
16
17 In a packed fragment, 0 - register value is the same as before
18 11 - raw register data follows. Only regbits[i]
19 bits, not full 8
20 10 - offset + number of bytes from preceding
21 data. As many bits as are required to hold
22 fragment offset & counter data (OFFNUM).
23
24 AYC information: http://www.cpcwiki.eu/index.php/AYC
25
26 */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "infos.h"
33 #include "common.h"
34 #include "lzh.h"
35
36 #define REGS 14
37 #define FRAG 128 // Number of rows to compress at a time
38 #define OFFNUM 14 // Bits needed to store off+num of FRAG
39
40
41
42 void writebits(unsigned data,int bits,char* &ptrWrite);
43
44 int main(int argc,char *argv[])
45 {
46 //
47 // Some initialization for the common library
48 //
49 SetApplicationParameters(
50 "Ym2Mym",
51 TOOL_VERSION_MAJOR,
52 TOOL_VERSION_MINOR,
53 "{ApplicationName} - Version {ApplicationVersion} - This program is a part of the OSDK\r\n"
54 "\r\n"
55 "Author:\r\n"
56 " First version by Marq/Lieves!Tuore & Fit (marq@iki.fi) \r\n"
57 " More recent updates by Pointier Mickael \r\n"
58 "\r\n"
59 "Purpose:\r\n"
60 " Convert Atari ST/Amstrad musics from the YM to MYM format.\r\n"
61 "\r\n"
62 "Usage:\r\n"
63 " {ApplicationName} source.ym destination.mym [load adress] [header name] \r\n"
64 "\r\n"
65 "Switches:\r\n"
66 " -tn Tuning\r\n"
67 " -t0 => No retune\r\n"
68 " -t1 => Double frequency [default]\r\n"
69 "\r\n"
70 " -hn Header\r\n"
71 " -h0 => No tape header [default]\r\n"
72 " -h1 => Use tape header (requires a start address and a name)\r\n"
73 "\r\n"
74 " -vn Verbosity.\r\n"
75 " -v0 => Silent [default]\r\n"
76 " -v1 => Shows information about what Ym2Mym is doing\r\n"
77 "\r\n"
78 " -mn Max size.\r\n"
79 " -m0 => No size limit [default]\r\n"
80 " -m1234 => Outputs an error if the exported size is too large\r\n"
81 "\r\n"
82 );
83
84 int maxSize=0;
85 int retune_music=1;
86 bool flagVerbosity=false;
87 bool flag_header=false;
88
89 ArgumentParser argumentParser(argc,argv);
90
91 while (argumentParser.ProcessNextArgument())
92 {
93 if (argumentParser.IsSwitch("-t"))
94 {
95 //format: [-t]
96 // 0 => No retune
97 // 1 => Double frequency [default]
98 retune_music=argumentParser.GetIntegerValue(0);
99 }
100 else
101 if (argumentParser.IsSwitch("-v"))
102 {
103 //testing: [-v]
104 // 0 => silent
105 // 1 => verbose
106 flagVerbosity=argumentParser.GetBooleanValue(true);
107 }
108 else
109 if (argumentParser.IsSwitch("-h"))
110 {
111 //format: [-h]
112 // 0 => suppress header
113 // 1 => save header (default)
114 flag_header=argumentParser.GetBooleanValue(true);
115 }
116 else
117 if (argumentParser.IsSwitch("-m"))
118 {
119 //format: [-m]
120 // 0 => no max size (default)
121 // other => maximum size
122 maxSize=argumentParser.GetIntegerValue(0);
123 }
124 }
125
126 int adress_start=0;
127 std::string headerName;
128
129 if (flag_header)
130 {
131 // Header mode: Four parameters
132 if (argumentParser.GetParameterCount()!=4)
133 {
134 //
135 // Wrong number of arguments
136 //
137 ShowError(0);
138 }
139
140 //
141 // Check start address
142 //
143 adress_start=ConvertAdress(argumentParser.GetParameter(2));
144 headerName=argumentParser.GetParameter(3);
145 }
146 else
147 {
148 // No header mode: Two parameters
149 if (argumentParser.GetParameterCount()!=2)
150 {
151 ShowError(0);
152 }
153 }
154
155 unsigned char*data[REGS]; // The unpacked YM data
156 unsigned current[REGS];
157
158 char ym_new=0;
159
160 long n,row;
161 long i,change,pack,biggest=0,hits,oldrow,remain,offi;
162 long regbits[REGS]={8,4,8,4, 8,4,5,8, 5,5,5,8, 8,8}; // Bits per PSG reg
163 long regand[REGS]={255,15,255,15, 255,15,31,255, 31,31,31,255, 255,255}; // AND values to mask out extra bits from register data
164
165 unsigned long length; // Song length
166
167 const char* sourceFilename(argumentParser.GetParameter(0));
168 void* pcBuffer;
169 size_t cBufferSize;
170 if (!LoadFile(sourceFilename,pcBuffer,cBufferSize))
171 {
172 ShowError("Can't load '%s'",sourceFilename);
173 }
174
175 const lzhHeader_t* header=(const lzhHeader_t*)pcBuffer;
176 if ( (cBufferSize>=sizeof(lzhHeader_t)) && (!strncmp(header->id,"-lh5-",5)) )
177 {
178 CLzhDepacker depacker;
179 int dstSize=header->original;
180 void *pDst=malloc(dstSize);
181 const char* packedData=(const char*)pcBuffer+sizeof(lzhHeader_t)+header->name_lenght+2;
182
183 if (!depacker.LzUnpack(packedData,header->packed,pDst,dstSize))
184 {
185 ShowError("Failed depacking '%s', wrong LHA format or corrupted file?",sourceFilename);
186 }
187 free(pcBuffer);
188 pcBuffer=pDst;
189 cBufferSize=dstSize;
190 }
191 const unsigned char* sourceData=(const unsigned char*)pcBuffer;
192
193 // Check if the file is compressed
194 length=cBufferSize-4;
195 if (!strncmp((const char*)sourceData,"YM2!",4)) // YM2 is ok
196 {
197 sourceData+=4;
198 // YM2!
199 // First four bytes is the ASCII identifier "YM2!".
200 }
201 else
202 if (!strncmp((const char*)sourceData,"YM3!",4)) // YM3 is ok
203 {
204 sourceData+=4;
205 // YM3!
206 // First four bytes is again the ASCII identifier "YM3!".
207 // ------------------------------------------------------
208 // Offset Size Name Value Contents
209 // ------------------------------------------------------
210 // 0 4 ID "YM3!" File type Identificator
211 // The next bytes are the data block of AY chip registers values.
212 //
213 // Registers are updates one time per VBL interrupt. If music length is N interrupts,
214 // then block consist first N bytes for register 0, further N bytes for register 1
215 // and so on. In total: N*14 bytes. The number of used VBL for music can be computed
216 // as follow: nvbl = (ymfile_size-4)/14;
217 //
218 // VBL1:
219 // store reg0,reg1,reg2,...,reg12,reg13 (14 regs)
220 // VBL2:
221 // store reg0,reg1,reg2,...,reg12,reg13 (14 regs)
222 // ..........
223 // VBLn:
224 // store reg0,reg1,reg2,...,reg12,reg13 (14 regs)
225 //
226 // If the current interrupt features no output to register 13 then the byte of the
227 // data block for this interrupt and for this register has the value 255 ($FF).
228 }
229 else
230 if (!strncmp((const char*)sourceData,"YM3b",4)) // YM3b is ok
231 {
232 sourceData+=4;
233 // YM3b!
234 //
235 // This format is nearly identical with YM3. It adds only the ability to use loops.
236 //
237 // First four bytes is the ASCII identifier "YM3b".
238 // The following bytes are the data block (see YM3 description).
239 // Last four bytes is a DWORD (32bits integers) data and contains the frame number
240 // at which the loop restart. That's all.
241 sourceData+=4; // Skip restart for YM3b
242 length-=4;
243 }
244 else
245 if (!strncmp((const char*)sourceData,"YM4!",4)) // YM4 is not yet ok
246 {
247 sourceData+=4;
248 // YM4!
249 // (note, should be similar to YM5, without extra infos)
250 printf("YM4! format is not yet supported.\n");
251 exit(EXIT_FAILURE);
252 }
253 else
254 if ( (!strncmp((const char*)sourceData,"YM5!",4)) || (!strncmp((const char*)sourceData,"YM6!",4)) ) // YM5 is ok but needs a different loader
255 {
256 sourceData+=4;
257 // YM5!
258 // This is the actual and most common used format and consist consists of additional information:
259 // chip frequency, player frequency, title, author name, comment and specific Atari ST data (Digi-Drum and SID effects).
260
261 // YM6!
262 // This format is equivalent to YM5! but can use yet another special Atari effect.
263 ym_new=1;
264 }
265 else
266 {
267 printf("Unknown file format '%s'.\n",sourceData);
268 exit(EXIT_FAILURE);
269 }
270
271 if (ym_new) // New YM5 format loader
272 {
273 sourceData+=8; // Skip 'LeOnArD' checkstring
274 for (n=length=0;n<4;n++) // Number of VBL's
275 {
276 length<<=8;
277 length+=*sourceData++;
278 }
279 length*=REGS;
280
281 sourceData+=3; // Skip first 3 bytes of info
282 if (!((*sourceData++)&1))
283 {
284 printf("Only interleaved data supported.\n");
285 return(EXIT_FAILURE);
286 }
287
288 if ((*sourceData++) || (*sourceData++)) // Number of digidrums
289 {
290 printf("Digidrums not supported.\n");
291 return(EXIT_FAILURE);
292 }
293
294 sourceData+=4; /* Skip external freq */
295 sourceData+=2; /* Skip VBL freq */
296 sourceData+=4; /* Skip loop position */
297 sourceData+=2; /* Skip additional data */
298
299 while((*sourceData++)) /* Skip song name */
300 ;
301 while((*sourceData++)) /* Skip author name */
302 ;
303 while((*sourceData++)) /* Skip comments */
304 ;
305 }
306
307 /* Old YM2/YM3 format loader */
308 for (n=0;n<REGS;n++) /* Allocate memory & read data */
309 {
310 /* Allocate extra fragment to make packing easier */
311 if ((data[n]=(unsigned char*)malloc(length/REGS+FRAG))==NULL)
312 {
313 printf("Out of memory.\n");
314 return(EXIT_FAILURE);
315 }
316 memset(data[n],0,length/REGS+FRAG);
317 memcpy(data[n],sourceData,length/REGS);
318 sourceData+=length/REGS;
319 }
320
321 if (retune_music)
322 {
323 unsigned int frame;
324 if (flagVerbosity)
325 {
326 printf("Retuning the frequency.\n");
327 }
328 for (frame=0;frame<=length/REGS;frame++)
329 {
330 int freqA= ( ((data[1][frame])<<8) | (data[0][frame]) )>>1;
331 int freqB= ( ((data[3][frame])<<8) | (data[2][frame]) )>>1;
332 int freqC= ( ((data[5][frame])<<8) | (data[4][frame]) )>>1;
333 int freqEnv= ( ((data[12][frame])<<8) | (data[11][frame]) )>>1;
334 int freqNoise= data[6][frame]>>1;
335
336 data[0][frame]=freqA & 255;
337 data[1][frame]=(freqA>>8);
338
339 data[2][frame]=freqB & 255;
340 data[3][frame]=(freqB>>8);
341
342 data[4][frame]=freqC & 255;
343 data[5][frame]=(freqC>>8);
344
345 data[6][frame]=freqNoise;
346
347 data[11][frame]=freqEnv & 255;
348 data[12][frame]=(freqEnv>>8);
349 }
350 }
351
352
353 if (ym_new) // Let's mask the extra YM5 data out
354 {
355 for (n=0;n<REGS;n++)
356 {
357 for (row=0;row<(long)(length/REGS);row++)
358 {
359 data[n][row]&=regand[n];
360 }
361 }
362 }
363
364
365 char* destinationBuffer=(char*)malloc(cBufferSize);
366 char* ptrWrite=destinationBuffer;
367
368 // Set current values to impossible
369 for (n=0;n<REGS;n++)
370 {
371 current[n]=0xffff;
372 }
373
374 *ptrWrite++=length/REGS&0xff; // Write tune length
375 *ptrWrite++=(length/REGS>>8)&255;
376
377 for (n=0;n<(long)(length/REGS);n+=FRAG) // Go through fragments...
378 {
379 for (i=0;i<REGS;i++) // ... for each register
380 {
381 for (row=change=0;row<FRAG;row++)
382 if (data[i][n+row]!=current[i])
383 change=1;
384
385 if (!change) // No changes in the whole fragment
386 {
387 writebits(0,1,ptrWrite);
388 continue; // Skip the next pass
389 }
390 else
391 {
392 writebits(1,1,ptrWrite);
393 }
394
395 for (row=0;row<FRAG;row++)
396 {
397 if (data[i][n+row]!=current[i])
398 {
399 change=1;
400 current[i]=data[i][n+row];
401
402 biggest=0;
403 if (n) // Skip first fragment
404 {
405 offi=0;
406 remain=FRAG-row;
407
408 // Go through the preceding data and try to find similar data
409 for (oldrow=0;oldrow<FRAG;oldrow++)
410 {
411 hits=0;
412 for (pack=0;pack<remain;pack++)
413 {
414 if (data[i][n+row+pack]==data[i][n-FRAG+row+oldrow+pack]
415 && oldrow+pack<FRAG)
416 hits++;
417 else
418 break;
419 }
420 if (hits>biggest) // Bigger sequence found
421 {
422 biggest=hits;
423 offi=oldrow;
424 }
425 }
426 }
427
428 if (biggest>1) // Could we pack data?
429 {
430 row+=biggest-1;
431 current[i]=data[i][n+row];
432 writebits(2,2,ptrWrite);
433 writebits((offi<<OFFNUM/2)+(biggest-1),OFFNUM,ptrWrite);
434 }
435 else // Nope, write raw bits
436 {
437 writebits(3,2,ptrWrite);
438 writebits(data[i][n+row],regbits[i],ptrWrite);
439 }
440 }
441 else // Same as former value, write 0
442 {
443 writebits(0,1,ptrWrite);
444 }
445 }
446 }
447 }
448
449 writebits(0,0,ptrWrite); // Pad to byte size
450
451 ptrdiff_t outputFileSize=(ptrWrite-destinationBuffer);
452
453 if (maxSize && (outputFileSize>maxSize))
454 {
455 ShowError("File '%s' is too large (%d bytes instead of maximum %d)",sourceFilename,outputFileSize,maxSize);
456 }
457
458 FILE* f;
459 if ((f=fopen(argumentParser.GetParameter(1),"wb"))==NULL)
460 {
461 printf("Cannot open destination file.\n");
462 return(EXIT_FAILURE);
463 }
464
465 if (flag_header)
466 {
467 //
468 // Write tape header
469 //
470 unsigned char Header[]=
471 {
472 0x16, // 0 Synchro
473 0x16, // 1
474 0x16, // 2
475 0x24, // 3
476
477 0x00, // 4
478 0x00, // 5
479
480 0x80, // 6
481
482 0x00, // 7 $80=BASIC Autostart $C7=Assembly autostart
483
484 0xbf, // 8 End address
485 0x40, // 9
486
487 0xa0, // 10 Start address
488 0x00, // 11
489
490 0x00 // 12
491 };
492
493 int adress_end =adress_start+outputFileSize-1;
494
495 Header[10]=(adress_start>>8);
496 Header[11]=(adress_start&255);
497
498 Header[8]=(adress_end>>8);
499 Header[9]=(adress_end&255);
500
501 //
502 // Write header
503 //
504 fwrite(Header,sizeof(Header),1,f);
505 fwrite(headerName.c_str(),headerName.size()+1,1,f); // Include the null terminator
506 }
507 fwrite(destinationBuffer,outputFileSize,1,f);
508
509 fclose(f);
510
511 free(pcBuffer);
512 free(destinationBuffer);
513
514 return EXIT_SUCCESS;
515 }
516
517 // Writes bits to a file. If bits is 0, pads to byte size.
518 void writebits(unsigned data,int bits,char* &ptrWrite)
519 {
520 static unsigned char byte=0;
521
522 static int off=0;
523
524 int n;
525
526 if (!bits && off)
527 {
528 off=byte=0;
529 *ptrWrite++=byte;
530 return;
531 }
532
533 // Go through the bits and write a whole byte if needed
534 for (n=0;n<bits;n++)
535 {
536 if (data&(1<<((bits-1)-n)))
537 byte|=0x80>>off;
538
539 if (++off==8)
540 {
541 *ptrWrite++=byte;
542 off=byte=0;
543 }
544 }
545 }
546

  ViewVC Help
Powered by ViewVC 1.1.26