|
GNM file formatThe Generator GNM log format is: header: 0: 'G' 1: 'N' 2: 'M' 3: fields per second (e.g. 50 or 60) interval = 0; while (!EOF) { data = readbyte()) // readbyte() reads the next byte from the file if ((data & 0xF0) == 0) { switch (data) { case 0: /* start of field marker (1/50th or 1/60th second elapsed) */ sampsperfield=(readbyte() << 8) + readbyte(); case 1: /* FM port 0 */ ymport=0; ymreg=readbyte(); ymdata=readbyte(); case 2: /* FM port 1 */ ymport=1; ymreg=readbyte(); ymdata=readbyte(); case 3: /* PSG */ write readbyte() to psg port; case 4: /* escaped sample data and mark of interval */ ymport=0; ymreg=0x2A; ymdata=readbyte(); interval=1; case 5: /* no sample data this interval */ interval=1; /* no change of register 0x2A */ } } else { /* unescaped sample data and mark of interval */ ymport=0; ymreg=0x2A; ymdata=data; interval=1; } if (interval) { interval = 0; update sound; } } Rationale The GYM file format does not support samples. Where sample data has been added to the GYM file format it is not possible to know where within the time period of a field the sample(s) occured, so any players have to make a guess. Samples within a GYM file are represented using an FM write which consists of three bytes, this is a huge waste of space. Design The field is split into a distinct number of intervals in order to increase accuracy. As an emulation format, the idea was to output X intervals where X is the number of lines of display, as emulators work in terms of video lines. The GNM format has been modified however to cope with any number of intervals within a field. You could divide a field into 4 events for example. This would mean 4 times the accuracy in placing events (in time) than the GYM format. Of course, the higher the number of intervals, the bigger the resulting file format. You can only accurately have one sample per interval because the interval is the only thing that tells you where in time you are within a field. Therefore, the GNM format uses the samples themselves as a marker for each interval. If there is no sample, then a special marker is inserted to represent no change. This makes the file format as concise as possible. Encoding Each field starts with a $0 byte marker followed by two bytes that indicate the number of intervals for this field. Each interval is marked by a sample. The samples are placed directly in the stream except for byte vales $0 to $f, in which case they are escaped with a $4 byte. If there was no sample written in the interval period then a $5 byte is inserted. Generator's implemention Generator works on a field at a time basis. Generator allocates a block of memory, which expands if required, that contains what we would write to disk assuming maximum samples are necessary (maximum in Generator's case is the number of lines in the display, i.e. 262 for NTSC). Nominally 8k is allocated for this purpose. YM/PSG writes are written into this block, however writes to FM address $2a are held pending until the end of each line. At the end of the line we write the (last) sample into the block (as-is or escaped with a $4 if necessary, or $5 if no change to FM register $2a was made). At the end of a field we can then decide the number of intervals that we wish to output into the file. If there were no samples in the entire period then we remove all interval markers ($5 bytes) and set the number of intervals to 0 for this field. We could leave a few intervals in to make FM more accurate, but Generator doesn't do this (not sure if it's necessary). Generator is of course open source so you are free to look at the actual implementation. |