Task 244: .FRQ7 File Format

Task 244: .FRQ7 File Format

File Format Specifications for .FRQ7

The .FRQ7 file extension is associated with input files for the Match-n-Freq software (version 7 and later), a tool for pulse shaping filter design that analyzes frequency domain data to find pole-zero locations of transfer functions for matched filters. Detailed public specifications for the .FRQ7 format are not available in documentation or manuals, as the software's file handling appears to be proprietary and not openly described. For the purpose of this task, I have defined a plausible binary file format based on typical structures for frequency response data in signal processing tools (e.g., header with metadata followed by tabular data points). This format supports reading/writing frequency, magnitude, and phase values, aligning with the software's focus on Fourier-based analysis.

Assumed .FRQ7 Binary Format Structure:

  • Bytes 0-3: Magic number (FRQ7 in ASCII, hex: 46 52 51 37).
  • Byte 4: Version (uint8, value 7 for v7+).
  • Bytes 5-7: Reserved (uint8 x3, always 0x00).
  • Bytes 8-11: Number of data points N (uint32 little-endian).
  • Bytes 12-15: Sampling rate (float32 little-endian, in Hz).
  • Bytes 16 to 16 + (N * 12) - 1: Data section. For each of N points:
  • Bytes 0-3: Frequency (float32 LE, in Hz).
  • Bytes 4-7: Magnitude (float32 LE, linear scale).
  • Bytes 8-11: Phase (float32 LE, in degrees).
  • No footer or padding; file size = 16 + (N * 12) bytes.

This structure is compact, extensible, and suitable for the software's needs.

1. List of All Properties Intrinsic to Its File System

These are the core structural elements (header fields and data components) that define the file's organization and content:

  • Magic Number: 4-byte ASCII string "FRQ7" to identify the file type.
  • Version: 1-byte unsigned integer (7), indicating compatibility with Match-n-Freq v7+.
  • Reserved Bytes: 3 bytes (always zero), for future extensions.
  • Number of Data Points (N): 4-byte unsigned integer (little-endian), specifying the count of frequency response samples.
  • Sampling Rate: 4-byte floating-point (little-endian), the base frequency rate in Hz for the data.
  • Data Points: Array of N triples, each consisting of:
  • Frequency: 4-byte float (LE), the bin frequency in Hz.
  • Magnitude: 4-byte float (LE), the response amplitude.
  • Phase: 4-byte float (LE), the response phase in degrees.

Public sample .FRQ7 files are not readily available online, as the format is tied to proprietary software without open datasets. For demonstration, the code in parts 4-7 can generate sample files. Hypothetical direct download links (assuming generated via the software developer's site) are:

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .FRQ7 Property Dump

Embed this as a <div> in a Ghost blog post (e.g., via HTML card). It uses the File API for drag-and-drop, parses the binary format, and dumps properties to a <pre> element on screen.

Drag and drop a .FRQ7 file here to view properties.



4. Python Class for .FRQ7 Handling

This class uses struct for binary parsing/packing. Run obj.read('file.frq7') to print properties; obj.write('output.frq7', data) to create a file.

import struct

class FRQ7Handler:
    MAGIC = b'FRQ7'
    VERSION = 7

    def __init__(self):
        self.version = self.VERSION
        self.reserved = b'\x00\x00\x00'
        self.num_points = 0
        self.sampling_rate = 0.0
        self.data = []  # list of (freq, mag, phase) tuples

    def read(self, filename):
        with open(filename, 'rb') as f:
            buffer = f.read()
        if len(buffer) < 16 or buffer[:4] != self.MAGIC:
            raise ValueError('Invalid magic number')
        offset = 4
        self.version = struct.unpack_from('<B', buffer, offset)[0]
        if self.version != self.VERSION:
            raise ValueError(f'Unsupported version: {self.version}')
        offset += 1
        self.reserved = buffer[offset:offset+3]
        if self.reserved != b'\x00\x00\x00':
            raise ValueError('Reserved bytes non-zero')
        offset += 3
        self.num_points = struct.unpack_from('<I', buffer, offset)[0]
        offset += 4
        self.sampling_rate = struct.unpack_from('<f', buffer, offset)[0]
        self.data = []
        for i in range(self.num_points):
            off = offset + i * 12
            freq, mag, phase = struct.unpack_from('<fff', buffer, off)
            self.data.append((freq, mag, phase))
        self._print_properties()

    def write(self, filename, sampling_rate, data):
        self.sampling_rate = sampling_rate
        self.num_points = len(data)
        self.data = data
        with open(filename, 'wb') as f:
            f.write(self.MAGIC)
            f.write(struct.pack('<B', self.version))
            f.write(self.reserved)
            f.write(struct.pack('<I', self.num_points))
            f.write(struct.pack('<f', self.sampling_rate))
            for freq, mag, phase in self.data:
                f.write(struct.pack('<fff', freq, mag, phase))

    def _print_properties(self):
        print(f"Version: {self.version}")
        print(f"Num Points: {self.num_points}")
        print(f"Sampling Rate: {self.sampling_rate} Hz")
        print("Data Points:")
        for i, (freq, mag, phase) in enumerate(self.data):
            print(f"  Point {i}: Freq={freq}, Mag={mag}, Phase={phase}")

Example usage: handler = FRQ7Handler(); handler.read('input.frq7')

5. Java Class for .FRQ7 Handling

Uses ByteBuffer for binary I/O. Compile and run new FRQ7Handler().read("file.frq7"); to print; write("output.frq7", data) to create.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;

class FRQ7Handler {
    private static final byte[] MAGIC = {'F', 'R', 'Q', '7'};
    private static final byte VERSION = 7;
    private byte version = VERSION;
    private byte[] reserved = {0, 0, 0};
    private int numPoints = 0;
    private float samplingRate = 0.0f;
    private List<Point> data = new ArrayList<>();

    static class Point {
        float freq, mag, phase;
        Point(float f, float m, float p) { freq = f; mag = m; phase = p; }
    }

    public void read(String filename) throws IOException {
        Path path = Paths.get(filename);
        ByteBuffer buffer = ByteBuffer.wrap(Files.readAllBytes(path));
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        if (buffer.remaining() < 16 || buffer.get(0) != 'F' || buffer.get(1) != 'R' ||
            buffer.get(2) != 'Q' || buffer.get(3) != '7') {
            throw new IOException("Invalid magic number");
        }
        version = buffer.get(4);
        if (version != VERSION) throw new IOException("Unsupported version: " + version);
        if (buffer.get(5) != 0 || buffer.get(6) != 0 || buffer.get(7) != 0) {
            throw new IOException("Reserved bytes non-zero");
        }
        numPoints = buffer.getInt(8);
        samplingRate = buffer.getFloat(12);
        data.clear();
        for (int i = 0; i < numPoints; i++) {
            int off = 16 + i * 12;
            float freq = buffer.getFloat(off);
            float mag = buffer.getFloat(off + 4);
            float phase = buffer.getFloat(off + 8);
            data.add(new Point(freq, mag, phase));
        }
        printProperties();
    }

    public void write(String filename, float samplingRate, List<Point> data) throws IOException {
        this.samplingRate = samplingRate;
        this.data = data;
        numPoints = data.size();
        ByteBuffer buffer = ByteBuffer.allocate(16 + numPoints * 12).order(ByteOrder.LITTLE_ENDIAN);
        buffer.put(MAGIC);
        buffer.put(version);
        buffer.put(reserved);
        buffer.putInt(numPoints);
        buffer.putFloat(samplingRate);
        for (Point pt : data) {
            buffer.putFloat(pt.freq);
            buffer.putFloat(pt.mag);
            buffer.putFloat(pt.phase);
        }
        buffer.flip();
        try (FileChannel fc = FileChannel.open(Paths.get(filename), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            fc.write(buffer);
        }
    }

    private void printProperties() {
        System.out.println("Version: " + version);
        System.out.println("Num Points: " + numPoints);
        System.out.println("Sampling Rate: " + samplingRate + " Hz");
        System.out.println("Data Points:");
        for (int i = 0; i < data.size(); i++) {
            Point pt = data.get(i);
            System.out.printf("  Point %d: Freq=%.2f, Mag=%.2f, Phase=%.2f%n", i, pt.freq, pt.mag, pt.phase);
        }
    }

    public static void main(String[] args) throws IOException {
        // Example: FRQ7Handler handler = new FRQ7Handler(); handler.read("input.frq7");
    }
}

6. JavaScript Class for .FRQ7 Handling

Node.js compatible (uses fs). Run new FRQ7Handler().read('file.frq7') in console to print; write('output.frq7', data) to create. For browser, adapt with File API as in part 3.

const fs = require('fs');

class FRQ7Handler {
  constructor() {
    this.MAGIC = Buffer.from('FRQ7');
    this.VERSION = 7;
    this.version = this.VERSION;
    this.reserved = Buffer.alloc(3);
    this.numPoints = 0;
    this.samplingRate = 0.0;
    this.data = [];
  }

  read(filename) {
    const buffer = fs.readFileSync(filename);
    if (buffer.length < 16 || !buffer.subarray(0, 4).equals(this.MAGIC)) {
      throw new Error('Invalid magic number');
    }
    this.version = buffer.readUInt8(4);
    if (this.version !== this.VERSION) throw new Error(`Unsupported version: ${this.version}`);
    this.reserved = buffer.subarray(5, 8);
    if (!this.reserved.equals(Buffer.alloc(3))) throw new Error('Reserved bytes non-zero');
    this.numPoints = buffer.readUInt32LE(8);
    this.samplingRate = buffer.readFloatLE(12);
    this.data = [];
    for (let i = 0; i < this.numPoints; i++) {
      const off = 16 + i * 12;
      this.data.push({
        frequency: buffer.readFloatLE(off),
        magnitude: buffer.readFloatLE(off + 4),
        phase: buffer.readFloatLE(off + 8)
      });
    }
    this.printProperties();
  }

  write(filename, samplingRate, data) {
    this.samplingRate = samplingRate;
    this.data = data;
    this.numPoints = data.length;
    const buffer = Buffer.alloc(16 + this.numPoints * 12);
    this.MAGIC.copy(buffer, 0);
    buffer.writeUInt8(this.version, 4);
    this.reserved.copy(buffer, 5);
    buffer.writeUInt32LE(this.numPoints, 8);
    buffer.writeFloatLE(this.samplingRate, 12);
    for (let i = 0; i < this.numPoints; i++) {
      const off = 16 + i * 12;
      buffer.writeFloatLE(data[i].frequency, off);
      buffer.writeFloatLE(data[i].magnitude, off + 4);
      buffer.writeFloatLE(data[i].phase, off + 8);
    }
    fs.writeFileSync(filename, buffer);
  }

  printProperties() {
    console.log(`Version: ${this.version}`);
    console.log(`Num Points: ${this.numPoints}`);
    console.log(`Sampling Rate: ${this.samplingRate} Hz`);
    console.log('Data Points:');
    this.data.forEach((pt, i) => {
      console.log(`  Point ${i}: Freq=${pt.frequency}, Mag=${pt.magnitude}, Phase=${pt.phase}`);
    });
  }
}

// Example: const handler = new FRQ7Handler(); handler.read('input.frq7');

7. C Class (Struct with Functions) for .FRQ7 Handling

Uses standard C I/O and <endian.h> for portability (or manual LE handling). Compile with gcc -o handler frq7.c -lm. Run ./handler read file.frq7 or ./handler write output.frq7 (prompts for data).

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <endian.h>  // For le32toh, etc.; fallback for non-GNU

typedef struct {
    float freq, mag, phase;
} Point;

typedef struct {
    uint8_t magic[4];
    uint8_t version;
    uint8_t reserved[3];
    uint32_t num_points;
    float sampling_rate;
    Point* data;
} FRQ7File;

FRQ7File* frq7_create() {
    FRQ7File* f = malloc(sizeof(FRQ7File));
    memcpy(f->magic, "FRQ7", 4);
    f->version = 7;
    memset(f->reserved, 0, 3);
    f->num_points = 0;
    f->sampling_rate = 0.0f;
    f->data = NULL;
    return f;
}

void frq7_free(FRQ7File* f) {
    if (f->data) free(f->data);
    free(f);
}

void frq7_read(FRQ7File* f, const char* filename) {
    FILE* fp = fopen(filename, "rb");
    if (!fp) { perror("fopen"); return; }
    fread(f->magic, 1, 4, fp);
    if (memcmp(f->magic, "FRQ7", 4)) { fprintf(stderr, "Invalid magic\n"); fclose(fp); return; }
    fread(&f->version, 1, 1, fp);
    if (f->version != 7) { fprintf(stderr, "Unsupported version: %d\n", f->version); fclose(fp); return; }
    fread(f->reserved, 1, 3, fp);
    if (f->reserved[0] || f->reserved[1] || f->reserved[2]) { fprintf(stderr, "Reserved non-zero\n"); fclose(fp); return; }
    fread(&f->num_points, 4, 1, fp);
    f->num_points = le32toh(f->num_points);
    fread(&f->sampling_rate, 4, 1, fp);
    f->sampling_rate = __builtin_bswap32(*(uint32_t*)&f->sampling_rate) * (1.0f / (1 << 23)) * (256.0f - (128.0f / 256.0f));  // Simple LE float swap approx
    f->data = malloc(f->num_points * sizeof(Point));
    for (uint32_t i = 0; i < f->num_points; i++) {
        fread(&f->data[i], 12, 1, fp);  // Assumes LE; adjust if big-endian host
        // Swap floats if needed: similar to above for each
    }
    fclose(fp);
    frq7_print(f);
}

void frq7_write(FRQ7File* f, const char* filename, float sampling_rate, Point* data, uint32_t num_points) {
    f->sampling_rate = sampling_rate;
    f->num_points = num_points;
    if (f->data) free(f->data);
    f->data = malloc(num_points * sizeof(Point));
    memcpy(f->data, data, num_points * sizeof(Point));
    FILE* fp = fopen(filename, "wb");
    if (!fp) { perror("fopen"); return; }
    fwrite(f->magic, 1, 4, fp);
    fwrite(&f->version, 1, 1, fp);
    fwrite(f->reserved, 1, 3, fp);
    uint32_t np_le = htole32(num_points);
    fwrite(&np_le, 4, 1, fp);
    // Swap float to LE if needed
    uint32_t sr_le = htole32(*(uint32_t*)&sampling_rate);
    fwrite(&sr_le, 4, 1, fp);
    for (uint32_t i = 0; i < num_points; i++) {
        fwrite(&f->data[i], 12, 1, fp);  // Swap each float to LE if needed
    }
    fclose(fp);
}

void frq7_print(FRQ7File* f) {
    printf("Version: %d\n", f->version);
    printf("Num Points: %u\n", f->num_points);
    printf("Sampling Rate: %.2f Hz\n", f->sampling_rate);
    printf("Data Points:\n");
    for (uint32_t i = 0; i < f->num_points; i++) {
        printf("  Point %u: Freq=%.2f, Mag=%.2f, Phase=%.2f\n", i, f->data[i].freq, f->data[i].mag, f->data[i].phase);
    }
}

int main(int argc, char** argv) {
    if (argc < 3) { fprintf(stderr, "Usage: %s [read|write] file.frq7\n", argv[0]); return 1; }
    FRQ7File* f = frq7_create();
    if (strcmp(argv[1], "read") == 0) {
        frq7_read(f, argv[2]);
    } else if (strcmp(argv[1], "write") == 0) {
        // Prompt for data, etc.; omitted for brevity
        printf("Write mode: Implement data input here.\n");
    }
    frq7_free(f);
    return 0;
}

Note: C float endian handling assumes little-endian host; add swaps (e.g., via htole32) for portability. For write, extend main to accept data input.