diff options
Diffstat (limited to 'src/lib/formats/hxchfe_dsk.cpp')
-rw-r--r-- | src/lib/formats/hxchfe_dsk.cpp | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/src/lib/formats/hxchfe_dsk.cpp b/src/lib/formats/hxchfe_dsk.cpp new file mode 100644 index 00000000000..cf959fdff32 --- /dev/null +++ b/src/lib/formats/hxchfe_dsk.cpp @@ -0,0 +1,691 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf + +/* + The hfe_format class implements the HFE format that is used for the Lotharek + floppy emulator. + + Format definition according to the official document: + + File header (0x0000 - 0x01FF, 512 bytes) + ---------------------------------------- + + typedef struct picfileformatheader_ + { + UINT8 HEADERSIGNATURE[8]; // 0: "HXCPICFE" + UINT8 formatrevision; // 8: Revision 0 + UINT8 number_of_track; // 9: Number of track in the file + UINT8 number_of_side; // 10: Number of valid side (Not used by the emulator) + UINT8 track_encoding; // 11: Track Encoding mode + // (Used for the write support - Please see the list above) + UINT16 bitRate; // 12: Bitrate in Kbit/s. Ex : 250=250000bits/s + // Max value : 500 + UINT16 floppyRPM; // 14: Rotation per minute (Not used by the emulator) + UINT8 floppyinterfacemode; // 16: Floppy interface mode. (Please see the list above.) + UINT8 dnu; // 17: Free + UINT16 track_list_offset; // 18: Offset of the track list LUT in block of 512 bytes + // (Ex: 1=0x200) + UINT8 write_allowed; // 20: The Floppy image is write protected ? + UINT8 single_step; // 21: 0xFF : Single Step – 0x00 Double Step mode + UINT8 track0s0_altencoding; // 22: 0x00 : Use an alternate track_encoding for track 0 Side 0 + UINT8 track0s0_encoding; // 23: alternate track_encoding for track 0 Side 0 + UINT8 track0s1_altencoding; // 24: 0x00 : Use an alternate track_encoding for track 0 Side 1 + UINT8 track0s1_encoding; // 25: alternate track_encoding for track 0 Side 1 + } picfileformatheader; + + Byte order for UINT16 is little endian. + + floppyintefacemode values are defined in the header file as floppymode_t, + track_encodings are defined as encoding_t + + track0s0_encoding is only valid when track0s0_altencoding==0xff + track0s1_encoding is only valid when track0s1_altencoding==0xff + + Track offset lookup table (at 0x0200) + ------------------------------------- + + typedef struct pictrack_ + { + UINT16 offset; // Offset of the track data in blocks of 512 bytes (Ex: 2=0x400) + UINT16 track_len; // Length of the track data in byte. + } pictrack; + + This table has a size of number_of_track*4 bytes. + + Track data + ---------- + + (first possible occurance at 0x0400) + + Each track is encoded in a sequence of cell levels which are represented + by bits in the data. + + +--------+--------+--------+--------+---- ........ ---+--------+--------+ + | Head 0 | Head 1 | Head 0 | Head 1 | Hea ........ 1 | Head 0 | Head 1 | + +--------+--------+--------+--------+---- ........ ---+--------+--------+ + | Block 0 | Block 1 | | Block n-1 | + + Each block (Head 0 + Head 1) is 0x200 bytes long, with 0x100 bytes for + each head. Block n-1 may be partially filled, e.g. with 64 bytes for + head 0 and 64 bytes for head 1. The contents for head 1 in block n-1 + start at offxet 0x100 nevertheless: + + +--------+--------+ + |]]]] 0 |]]]] 1 | + +--------+--------+ + | Block n-1 | + + + Each byte in the track data is a sequence of cell sample levels + according to the sample rate. Bit order is little endian: + + Bits + 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 + + 0-bits indicate no change, 1-bits indicate flux level change. + + To encode the byte 0x4e in MFM at 250 kbit/s, the following data bytes + are used: + + Byte: 0 1 0 0 1 1 1 0 + MDM encoding: 10 01 00 10 01 01 01 00 + Reversed order: 0010 1010 0100 1001 = 2a 49 + + Interestingly, FM-encoded disks are usually sampled at 250 kbit/s, + like MFM, although FM only delivers 125 kbit/s. This oversampling leads + to zero bits (no change) every two positions. See below for details. + + TODO: + - Handle double-stepping for medium.tracks=40, drive.tracks=80 +*/ + +#include "hxchfe_dsk.h" + +#define HEADER_LENGTH 512 +#define TRACK_TABLE_LENGTH 1024 + +hfe_format::hfe_format() : floppy_image_format_t(), + m_cylinders(0), + m_heads(0), + m_track_encoding(UNKNOWN_ENCODING), + m_bit_rate(0), + m_floppy_rpm(0), + m_interface_mode(DISABLE_FLOPPYMODE), + m_write_allowed(true), + m_single_step(true), + m_track0s0_has_altencoding(false), + m_track0s0_encoding(UNKNOWN_ENCODING), + m_track0s1_has_altencoding(false), + m_track0s1_encoding(UNKNOWN_ENCODING), + m_selected_mode(DISABLE_FLOPPYMODE), + m_selected_encoding(UNKNOWN_ENCODING) +{ +} + +const char *hfe_format::name() const +{ + return "hfe"; +} + +const char *hfe_format::description() const +{ + return "SDCard HxC Floppy Emulator HFE File format"; +} + +const char *hfe_format::extensions() const +{ + return "hfe"; +} + +bool hfe_format::supports_save() const +{ + return true; +} + +int hfe_format::identify(io_generic *io, UINT32 form_factor) +{ + UINT8 header[8]; + + io_generic_read(io, &header, 0, sizeof(header)); + if ( memcmp( header, HFE_FORMAT_HEADER, 8 ) ==0) { + return 100; + } + return 0; +} + +bool hfe_format::load(io_generic *io, UINT32 form_factor, floppy_image *image) +{ + UINT8 header[HEADER_LENGTH]; + UINT8 track_table[TRACK_TABLE_LENGTH]; + + int drivecyl, driveheads; + image->get_maximal_geometry(drivecyl, driveheads); + + // read header + io_generic_read(io, header, 0, HEADER_LENGTH); + + // get values + // Format revision must be 0 + if (header[8] != 0) + { + osd_printf_error("hxchfe: Invalid format revision. Expected 0, got %d.\n", header[8]); + return false; + } + + m_cylinders = header[9] & 0xff; + m_heads = header[10] & 0xff; + + if (drivecyl < m_cylinders) + { + osd_printf_error("hxchfe: Floppy disk has too many tracks for this drive (floppy tracks=%d, drive tracks=%d).\n", m_cylinders, drivecyl); + return false; + } + + if (m_cylinders <= drivecyl/2) + { + osd_printf_error("hxchfe: Double stepping not yet supported (floppy tracks=%d, drive tracks=%d).\n", m_cylinders, drivecyl); + return false; + } + + m_track_encoding = (encoding_t)(header[11] & 0xff); + + if (m_track_encoding > EMU_FM_ENCODING) + { + osd_printf_error("hxchfe: Unknown track encoding %d.\n", m_track_encoding); + return false; + } + + m_bit_rate = (header[12] & 0xff) | ((header[13] & 0xff)<<8); + + if (m_bit_rate > 500) + { + osd_printf_error("hxchfe: Unsupported bit rate %d.\n", m_bit_rate); + return false; + } + int samplelength = 500000 / m_bit_rate; + + // Not used in the HxC emulator + m_floppy_rpm = (header[14] & 0xff) | ((header[15] & 0xff)<<8); + + m_interface_mode = (floppymode_t)(header[16] & 0xff); + if (m_interface_mode > S950_HD_FLOPPYMODE) + { + osd_printf_error("hxchfe: Unknown interface mode %d.\n", m_interface_mode); + return false; + } + + m_write_allowed = (header[20] != 0); + m_single_step = (header[21] != 0); + m_track0s0_has_altencoding = (header[22] == 0x00); + m_track0s0_encoding = (encoding_t)(header[23] & 0xff); + m_track0s1_has_altencoding = (header[24] == 0x00); + m_track0s1_encoding = (encoding_t)(header[25] & 0xff); + + // read track lookup table (multiple of 512) + int table_offset = (header[18] & 0xff) | ((header[19] & 0xff)<<8); + + io_generic_read(io, track_table, table_offset<<9, TRACK_TABLE_LENGTH); + + for (int i=0; i < m_cylinders; i++) + { + m_cyl_offset[i] = (track_table[4*i] & 0xff) | ((track_table[4*i+1] & 0xff)<<8); + m_cyl_length[i] = (track_table[4*i+2] & 0xff) | ((track_table[4*i+3] & 0xff)<<8); + } + + // Load the tracks + dynamic_buffer cylinder_buffer; + for(int cyl=0; cyl < m_cylinders; cyl++) + { + // actual data read + // The HFE format defines an interleave of the two sides per cylinder + // at every 256 bytes + cylinder_buffer.resize(m_cyl_length[cyl]); + io_generic_read(io, &cylinder_buffer[0], m_cyl_offset[cyl]<<9, m_cyl_length[cyl]); + + generate_track_from_hfe_bitstream(cyl, 0, samplelength, &cylinder_buffer[0], m_cyl_length[cyl], image); + if (m_heads == 2) + generate_track_from_hfe_bitstream(cyl, 1, samplelength, &cylinder_buffer[0], m_cyl_length[cyl], image); + } + + bool success = true; + + // Find variant + if (m_track_encoding == ISOIBM_FM_ENCODING || m_track_encoding == EMU_FM_ENCODING) + // FM is for single density + image->set_variant((m_heads==1)? floppy_image::SSSD : floppy_image::DSSD); + else + { + // MFM encoding is for everything else + if (m_track_encoding == ISOIBM_MFM_ENCODING || m_track_encoding == AMIGA_MFM_ENCODING) + { + // Each cylinder contains the samples of both sides, 8 samples per + // byte; the bitRate determines how many samples constitute a cell + + // DSDD: 360 KiB (5.25")= 2*40*18*256; 100000 cells/track, 2 us, bit rate = 250 kbit/s + // DSDD: 720 KiB (3.5") = 2*80*18*256; 100000 cells/track, 2 us, 250 kbit/s + // DSHD: 1.4 MiB = 2*80*18*512 bytes; 200000 cells/track, 1 us, 500 kbit/s + // DSED: 2.8 MiB = 2*80*36*512 bytes; 400000 cells/track, 500 ns, 1 Mbit/s + + // Use cylinder 1 (cyl 0 may have special encodings) + int cellcount = (m_cyl_length[1] * 8 / 2) * 250 / m_bit_rate; + if (cellcount > 300000) + image->set_variant(floppy_image::DSED); + else + { + if (cellcount > 150000) + image->set_variant(floppy_image::DSHD); + else + { + if (cellcount > 90000) + // We cannot distinguish DSDD from DSQD without knowing the size of the floppy disk + image->set_variant((m_heads==1)? floppy_image::SSDD : floppy_image::DSDD); + } + } + } + else + success = false; + } + return success; +} + +void hfe_format::generate_track_from_hfe_bitstream(int cyl, int head, int samplelength, const UINT8 *trackbuf, int track_end, floppy_image *image) +{ + // HFE has a minor issue: The track images do not sum up to 200 ms. + // Tracks are samples at 250 kbit/s for both FM and MFM, which yields + // 50000 data bits (100000 samples) for MFM, while FM is twice oversampled + // (4 samples per actual data bit) + // Hence, for both FM and MFM, we need 100000 samples. + + // Track length 61B0 (both sides, FM) + // 100 + 100 + ... + 100 + (B0+50) = 3000 + B0 + 50 (pad) + // 100 + 100 + .... + 100 + B0 = 3000 + B0 = 99712 samples (-288) + + // Track length 61C0 (both sides, MFM) + // 100 + 100 + ... + 100 + (C0+40) = 3000 + C0 + 40 (pad) + // 100 + 100 + .... + 100 + C0 = 3000 + C0 = 99840 samples (-160) + + // Solution: Repeat the last byte until we have enough samples + // Note: We do not call normalize_times here because we're doing the job here + + // HFE does not define subtracks; set to 0 + + // MG_1 / MG_0 are (logical) levels that indicate transition / no change + // MG_A / MG_B are physical flux directions + // + // Cell: | AAAABBBB | = MG_1 = | BBBBAAAA | + // | AAAAAAAA | = MG_0 = | BBBBBBBB | + + std::vector<UINT32> &dest = image->get_buffer(cyl, head, 0); + dest.clear(); + + // Start with MG_A + UINT32 cbit = floppy_image::MG_A; + + int offset = 0x100; + + if (head==0) + { + offset = 0; + track_end -= 0x0100; + } + + UINT8 current = 0; + int time = 0; + + dest.push_back(cbit | time); + + cbit = floppy_image::MG_B; + + // Oversampled FM images (250 kbit/s) start with a 0, where a 1 is + // expected for 125 kbit/s. + // In order to make an oversampled image look like a normally sampled one, + // we position the transition at 500 ns before the cell end. + // The HFE format has a 1 us minimum cell size; this means that a normally + // sampled FM image with 11111... at the begining means + // 125 kbit/s: 1 1 1 1 1... + // 250 kbit/s: 01 01 01 01 01... + // 500 kbit/s: 00010001000100010001... + // + // -500 3500 7500 11500 + // +-|---:---|---:-+ | : | : +-|---:---|---:-+ | + // | | : | : | | : | : | | : | : | | + // | | : | : +-|---:---|---:-+ | : | : +-| + // -500 0 2000 4000 6000 8000 10000 12000 + // + // 3500 (1) samplelength - 500 + // 7500 (1) +samplelength + // 11500 (1) +samplelength + // 15500 (1) +samplelength + // + // Double samples + // + // 1500 (0) samplelength - 500 + // 3500 (1) +samplelength + // 5500 (0) +samplelength + // 7500 (1) +samplelength + // 9500 (0) +samplelength + // 11500 (1) +samplelength + + time = -500; + + // We are creating a sequence of timestamps with flux info + // Note that the flux change occurs in the last quarter of a cell + + while (time < 200000000) // one rotation in nanosec + { + current = trackbuf[offset]; + for (int j=0; j < 8; j++) + { + time += samplelength; + if ((current & 1)!=0) + { + // Append another transition to the vector + dest.push_back(cbit | time); + + // Toggle the cell level + cbit = (cbit == floppy_image::MG_A)? floppy_image::MG_B : floppy_image::MG_A; + } + + // HFE uses little-endian bit order + current >>= 1; + } + offset++; + if ((offset & 0xff)==0) offset += 0x100; + + // When we have not reached the track end (after 0.2 sec) but run + // out of samples, repeat the last value + if (offset >= track_end) offset = track_end - 1; + } + + image->set_write_splice_position(cyl, head, 0, 0); +} + +bool hfe_format::save(io_generic *io, floppy_image *image) +{ + dynamic_buffer cylbuf; + cylbuf.resize(0x6200); + + UINT8 header[HEADER_LENGTH]; + UINT8 track_table[TRACK_TABLE_LENGTH]; + + int track_end = 0x61c0; + int samplelength = 2000; + + // Set up header + const char* sig = "HXCPICFE"; + memcpy(header, sig, 8); + + header[8] = 0; + // Can we change the number of tracks or heads? + image->get_actual_geometry(m_cylinders, m_heads); + + header[9] = m_cylinders; + header[10] = m_heads; + // Floppy RPM is not used + header[14] = 0; + header[15] = 0; + + // Bit rate and encoding will be set later, they may have changed by + // reformatting. The selected encoding is UNKNOWN_ENCODING unless + // explicitly set + m_track_encoding = m_selected_encoding; + + // Take the old mode, unless we have specified a mode + header[16] = (m_selected_mode != DISABLE_FLOPPYMODE)? m_selected_mode : m_interface_mode; + header[17] = 0; + + // The track lookup table is located at offset 0x200 (as 512 multiple) + header[18] = 1; + header[19] = 0; + + header[20] = m_write_allowed? 0xff : 0x00; + header[21] = m_single_step? 0xff : 0x00; + + // TODO: Allow for divergent track 0 format + header[22] = m_track0s0_has_altencoding? 0x00 : 0xff; + header[23] = m_track0s0_encoding; + header[24] = m_track0s1_has_altencoding? 0x00 : 0xff; + header[25] = m_track0s1_encoding; + + // Fill the remaining bytes with 0xff + for (int i=26; i < HEADER_LENGTH; i++) header[i] = 0xff; + + // Don't write yet; we still have to find out the bit rate. + + // We won't have more than 200000 cells on the track + for (int cyl=0; cyl < m_cylinders; cyl++) + { + // After the call, the encoding will be set to FM or MFM + generate_hfe_bitstream_from_track(cyl, 0, samplelength, m_track_encoding, &cylbuf[0], track_end, image); + if (m_heads == 2) + generate_hfe_bitstream_from_track(cyl, 1, samplelength, m_track_encoding, &cylbuf[0], track_end, image); + + if (cyl==0) + { + // Complete the header and write it + header[11] = m_track_encoding; + m_bit_rate = 500000/samplelength; + header[12] = m_bit_rate & 0xff; + header[13] = (m_bit_rate >> 8) & 0xff; + + // Now write the header + io_generic_write(io, header, 0, HEADER_LENGTH); + + // Set up the track lookup table + // We need the encoding value to be sure about the track length + int len = (m_track_encoding==ISOIBM_FM_ENCODING)? 0x61b0 : 0x61c0; + int pos = 0x400; + + for (int i=0; i < m_cylinders; i++) + { + m_cyl_offset[i] = (pos >> 9); + m_cyl_length[i] = len; + pos += (len + 0x1ff) & 0xfe00; + track_table[i*4] = m_cyl_offset[i] & 0xff; + track_table[i*4+1] = (m_cyl_offset[i]>>8) & 0xff; + track_table[i*4+2] = len & 0xff; + track_table[i*4+3] = (len>>8) & 0xff; + } + // Set the remainder to 0xff + for (int i=m_cylinders*4; i < TRACK_TABLE_LENGTH; i++) + track_table[i] = 0xff; + + io_generic_write(io, track_table, 0x200, TRACK_TABLE_LENGTH); + } + // Write the current cylinder + io_generic_write(io, &cylbuf[0], m_cyl_offset[cyl]<<9, (m_cyl_length[cyl] + 0x1ff) & 0xfe00); + } + return true; +} + +void hfe_format::generate_hfe_bitstream_from_track(int cyl, int head, int& samplelength, encoding_t& encoding, UINT8 *cylinder_buffer, int track_end, floppy_image *image) +{ + // We are using an own implementation here because the result of the + // parent class method would require some post-processing that we + // can easily avoid. + + // See floppy_image_format_t::generate_bitstream_from_track + // as the original code + + // No subtracks definded + std::vector<UINT32> &tbuf = image->get_buffer(cyl, head, 0); + if (tbuf.size() <= 1) + { + // Unformatted track + // TODO must handle that according to HFE + int track_size = 200000000/samplelength; + memset(cylinder_buffer, 0, (track_size+7)/8); + return; + } + + // Find out whether we have FM or MFM recording, and determine the bit rate. + // This is needed for the format header. + // + // The encoding may have changed by reformatting; we cannot rely on the + // header when loading. + // + // FM: encoding 1 -> flux length = 4 us (min) ambivalent + // encoding 10 -> flux length = 8 us (max) ambivalent + // MFM: encoding 10 -> flux length = 4 us (min, DD) ambivalent + // encoding 100 -> flux length = 6 us (DD) significant + // encoding 1000 -> flux length = 8 us (max, DD) ambivalent + // encoding 10 -> flux length = 2 us (min, HD) significant + // encoding 100 -> flux length = 3 us (max, HD) significant + + // If we have MFM, we should very soon detect a flux length of 6 us. + // But if we have FM, how long should we search to be sure? + // We assume that after 2000 us we should have reached the first IDAM, + // which contains a sequence 1001, implying a flux length of 6 us. + // If there was no such flux in that area, this can safely be assumed to be FM. + + // Do it only for the first track; the format only supports one encoding. + if (encoding == UNKNOWN_ENCODING) + { + bool mfm_recording = false; + int time0 = 0; + int minflux = 4000; + // Skip the beginning (may have a short cell) + for (int i=2; (i < tbuf.size()-1) && (time0 < 2000000) && !mfm_recording; i++) + { + int time0 = tbuf[i] & floppy_image::TIME_MASK; + int fluxlen = (tbuf[i+1] & floppy_image::TIME_MASK) - time0; + if ((fluxlen < 3500) || (fluxlen > 5500 && fluxlen < 6500)) + mfm_recording = true; + if (fluxlen < minflux) minflux = fluxlen; + } + encoding = mfm_recording? ISOIBM_MFM_ENCODING : ISOIBM_FM_ENCODING; + + // samplelength = 1000ns => 10^6 cells/sec => 500 kbit/s + // samplelength = 2000ns => 250 kbit/s + // We stay with double sampling at 250 kbit/s for FM + if (minflux < 3500) samplelength = 1000; + else samplelength = 2000; + } + + // Start at the write splice + UINT32 splice = image->get_write_splice_position(cyl, head, 0); + + int cur_pos = splice; + int cur_entry = 0; + + // Fast-forward to the write splice position (always 0 in this format) + while (cur_entry < int(tbuf.size())-1 && (tbuf[cur_entry+1] & floppy_image::TIME_MASK) < cur_pos) + cur_entry++; + + int period = samplelength; + int period_adjust_base = period * 0.05; + + int min_period = int(samplelength*0.75); + int max_period = int(samplelength*1.25); + int phase_adjust = 0; + int freq_hist = 0; + UINT32 next = 0; + + int offset = 0x100; + + // Prepare offset for the format storage + if (head==0) + { + offset = 0; + track_end -= 0x0100; + } + + UINT8 bit = 0x01; + UINT8 current = 0; + + while (next < 200000000) { + int edge = tbuf[cur_entry] & floppy_image::TIME_MASK; + + // Start of track? Use next entry. + if (edge==0) + { + cur_pos = 0; + edge = tbuf[++cur_entry] & floppy_image::TIME_MASK; + } + + // Wrapped over end? + if (edge < cur_pos) edge += 200000000; + + // End of cell + next = cur_pos + period + phase_adjust; + + // End of the window is at next; edge is the actual transition + if (edge >= next) + { + // No transition in the window -> 0 + phase_adjust = 0; + } + else + { + // Transition in the window -> 1 + current |= bit; + int delta = edge - (next - period/2); + + phase_adjust = 0.65*delta; + + if (delta < 0) + { + if (freq_hist < 0) freq_hist--; + else freq_hist = -1; + } + else + { + if (delta > 0) + { + if(freq_hist > 0) freq_hist++; + else freq_hist = 1; + } + else freq_hist = 0; + } + + if (freq_hist) + { + int afh = freq_hist < 0 ? -freq_hist : freq_hist; + if (afh > 1) + { + int aper = period_adjust_base*delta/period; + if (!aper) + aper = freq_hist < 0 ? -1 : 1; + period += aper; + + if (period < min_period) period = min_period; + else if (period > max_period) period = max_period; + } + } + } + + cur_pos = next; + if(cur_pos >= 200000000) { + cur_pos -= 200000000; + cur_entry = 0; + } + + bit = (bit << 1) & 0xff; + if (bit == 0) + { + bit = 0x01; + cylinder_buffer[offset++] = current; + if ((offset & 0xff)==0) offset += 0x100; + current = 0; + } + + // Fast-forward to next cell + while (cur_entry < int(tbuf.size())-1 && (tbuf[cur_entry] & floppy_image::TIME_MASK) < cur_pos) + cur_entry++; + + // Reaching the end of the track + if (cur_entry == int(tbuf.size())-1 && (tbuf[cur_entry] & floppy_image::TIME_MASK) < cur_pos) + { + // Wrap to index 0 or 1 depending on whether there is a transition exactly at the index hole + cur_entry = (tbuf[int(tbuf.size())-1] & floppy_image::MG_MASK) != (tbuf[0] & floppy_image::MG_MASK) ? + 0 : 1; + } + } + // Write the current byte when not done + if (bit != 0x01) + cylinder_buffer[offset] = current; +} + +const floppy_format_type FLOPPY_HFE_FORMAT = &floppy_image_format_creator<hfe_format>; + + |