Task 787: .VPR File Format

Task 787: .VPR File Format

1. Properties of the .VPR File Format

The .VPR file format, used by Yamaha VOCALOID5 and VOCALOID6, is a ZIP archive containing project data. The primary file within the archive is Project/sequence.json, which stores the project structure in JSON format. Other files may include audio assets for audio tracks (e.g., WAV files), but the core properties are defined in the JSON. The properties intrinsic to the format (i.e., the structured fields in the JSON) are as follows, organized hierarchically with types where applicable. These represent the data elements for musical notes, tracks, tempo, and synthesis settings. Time values are typically in ticks (480 ticks per quarter note).

Root (Vpr):

  • Title: string (project title)
  • MasterTrack: object (global project settings)
  • Tracks: array of Track objects (list of vocal and audio tracks)
  • Voices: array of Voice objects (voice bank references, optional in some projects)

MasterTrack:

  • SamplingRate: integer (e.g., 44100)
  • Loop: object (loop settings)
  • Tempo: object (tempo control)
  • TimeSig: object (time signature control)
  • Volume: object (master volume automation)

Loop:

  • IsEnabled: boolean
  • Begin: integer (start position in ticks)
  • End: integer (end position in ticks)

Tempo:

  • IsFolded: boolean (UI state for folding in editor)
  • Height: double (UI height in editor)
  • Global: object (global tempo setting)
  • Events: array of TempoEvent objects (tempo changes)

Global (under Tempo):

  • IsEnabled: boolean
  • Value: integer (global BPM * 100)

TempoEvent:

  • Pos: integer (position in ticks)
  • Value: integer (BPM * 100)

TimeSig:

  • IsFolded: boolean
  • Events: array of TimeSigEvent objects (time signature changes)

TimeSigEvent:

  • Bar: integer (bar number)
  • Numer: integer (numerator, e.g., 4)
  • Denom: integer (denominator, e.g., 4)

Volume (under MasterTrack or Track):

  • IsFolded: boolean
  • Height: double
  • Events: array of VolumeEvent objects (volume automation points)

VolumeEvent (inferred structure):

  • Pos: integer (position in ticks)
  • Value: integer (volume level)

Track:

  • Type: integer (0 for vocal/singing track, 1 for audio track)
  • Name: string (track name)
  • Color: integer (track color code)
  • BusNo: integer (bus assignment)
  • IsFolded: boolean
  • Height: double
  • Volume: Volume object (track volume automation)
  • Panpot: Panpot object (pan automation)
  • IsMuted: boolean
  • IsSoloMode: boolean
  • Parts: array of Part objects (segments within the track)

Panpot:

  • IsFolded: boolean
  • Height: double
  • Events: array of PanpotEvent objects (pan automation points)

PanpotEvent (inferred structure):

  • Pos: integer
  • Value: integer (pan value, -127 to 127)

Part:

  • Pos: integer (start position in ticks)
  • Duration: integer (length in ticks)
  • StyleName: string (singing style name)
  • Voice: Voice object (voice bank used)
  • MidiEffects: array of MidiEffect objects (effects applied)
  • Notes: array of Note objects (musical notes)

Voice:

  • CompID: string (component ID of voice bank)
  • LangID: integer (language ID, e.g., 0 for Japanese)

MidiEffect:

  • Id: string (effect identifier)
  • IsBypassed: boolean
  • IsFolded: boolean
  • Parameters: array of Parameter objects (effect parameters)

Parameter:

  • Name: string (parameter name)
  • Value: integer (parameter value)

Note:

  • Lyric: string (lyric text)
  • Phoneme: string (phonetic pronunciation)
  • IsProtected: boolean (protect from auto-edits)
  • Pos: integer (relative position in ticks within part)
  • Duration: integer (length in ticks)
  • Number: integer (MIDI note number, 0-127)
  • Velocity: integer (velocity, 0-127)
  • Exp: Exp object (expression settings)
  • SingingSkill: SingingSkill object (singing technique)
  • Vibrato: Vibrato object (vibrato settings)

Exp:

  • Opening: integer (breathiness or opening value)

SingingSkill:

  • Duration: integer (skill duration)
  • Weight: Weight object (weighting for skill application)

Weight:

  • Pre: integer (pre-note weight)
  • Post: integer (post-note weight)

Vibrato:

  • Type: integer (vibrato type)
  • Duration: integer (vibrato length)

Note: Additional automation curves (e.g., for pitch bend, dynamics) may exist as events in parts or tracks, but they follow similar pos/value structures.

.VPR files are often distributed within ZIP archives for convenience. The following links provide direct downloads to ZIP files containing .VPR files:

3. HTML/JavaScript for Drag-and-Drop .VPR File Dump

The following is a self-contained HTML page with embedded JavaScript that can be embedded in a blog (e.g., Ghost). It allows users to drag and drop a .VPR file, extracts the Project/sequence.json from the ZIP, parses it, and dumps all properties to the screen in a structured format.

VPR File Dumper
Drag and drop .VPR file here

4. Python Class for .VPR Handling

import zipfile
import json
import os

class VprHandler:
    def __init__(self, filepath=None):
        self.filepath = filepath
        self.data = None
        if filepath:
            self.read(filepath)

    def read(self, filepath):
        self.filepath = filepath
        with zipfile.ZipFile(filepath, 'r') as z:
            if 'Project/sequence.json' in z.namelist():
                with z.open('Project/sequence.json') as f:
                    self.data = json.load(f)
            else:
                raise ValueError('Project/sequence.json not found in .VPR file.')

    def print_properties(self):
        if self.data:
            print(json.dumps(self.data, indent=4))
        else:
            print('No data loaded.')

    def write(self, output_filepath):
        if not self.data:
            raise ValueError('No data to write.')
        temp_dir = 'temp_vpr'
        os.makedirs(temp_dir, exist_ok=True)
        json_path = os.path.join(temp_dir, 'Project')
        os.makedirs(json_path, exist_ok=True)
        with open(os.path.join(json_path, 'sequence.json'), 'w') as f:
            json.dump(self.data, f, indent=4)
        with zipfile.ZipFile(output_filepath, 'w') as z:
            for root, _, files in os.walk(temp_dir):
                for file in files:
                    z.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), temp_dir))
        os.rmdir(os.path.join(temp_dir, 'Project'))
        os.rmdir(temp_dir)

# Example usage:
# handler = VprHandler('example.vpr')
# handler.print_properties()
# handler.data['title'] = 'Modified Title'  # Modify as needed
# handler.write('modified.vpr')

5. Java Class for .VPR Handling

import java.io.*;
import java.util.zip.*;
import org.json.*;

public class VprHandler {
    private String filepath;
    private JSONObject data;

    public VprHandler(String filepath) throws IOException {
        this.filepath = filepath;
        read(filepath);
    }

    public void read(String filepath) throws IOException {
        this.filepath = filepath;
        try (ZipFile zf = new ZipFile(filepath)) {
            ZipEntry entry = zf.getEntry("Project/sequence.json");
            if (entry != null) {
                try (InputStream is = zf.getInputStream(entry)) {
                    data = new JSONObject(new JSONTokener(is));
                }
            } else {
                throw new IOException("Project/sequence.json not found.");
            }
        }
    }

    public void printProperties() {
        if (data != null) {
            System.out.println(data.toString(4));
        } else {
            System.out.println("No data loaded.");
        }
    }

    public void write(String outputFilepath) throws IOException {
        if (data == null) {
            throw new IOException("No data to write.");
        }
        File tempDir = new File("temp_vpr");
        tempDir.mkdirs();
        File projectDir = new File(tempDir, "Project");
        projectDir.mkdirs();
        try (FileWriter fw = new FileWriter(new File(projectDir, "sequence.json"))) {
            fw.write(data.toString(4));
        }
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFilepath))) {
            addToZip(tempDir, zos, "");
        }
        deleteDir(tempDir);
    }

    private void addToZip(File dir, ZipOutputStream zos, String base) throws IOException {
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                addToZip(file, zos, base + file.getName() + "/");
            } else {
                try (FileInputStream fis = new FileInputStream(file)) {
                    zos.putNextEntry(new ZipEntry(base + file.getName()));
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = fis.read(buffer)) > 0) {
                        zos.write(buffer, 0, len);
                    }
                    zos.closeEntry();
                }
            }
        }
    }

    private void deleteDir(File dir) {
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                deleteDir(file);
            } else {
                file.delete();
            }
        }
        dir.delete();
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     VprHandler handler = new VprHandler("example.vpr");
    //     handler.printProperties();
    //     handler.write("modified.vpr");
    // }
}

6. JavaScript Class for .VPR Handling

This class is for Node.js, using adm-zip for ZIP handling and built-in JSON.

const AdmZip = require('adm-zip');
const fs = require('fs');

class VprHandler {
    constructor(filepath = null) {
        this.filepath = filepath;
        this.data = null;
        if (filepath) {
            this.read(filepath);
        }
    }

    read(filepath) {
        this.filepath = filepath;
        const zip = new AdmZip(filepath);
        const entry = zip.getEntry('Project/sequence.json');
        if (entry) {
            this.data = JSON.parse(zip.readAsText(entry));
        } else {
            throw new Error('Project/sequence.json not found.');
        }
    }

    printProperties() {
        if (this.data) {
            console.log(JSON.stringify(this.data, null, 4));
        } else {
            console.log('No data loaded.');
        }
    }

    write(outputFilepath) {
        if (!this.data) {
            throw new Error('No data to write.');
        }
        const tempDir = 'temp_vpr';
        fs.mkdirSync(tempDir, { recursive: true });
        const projectDir = `${tempDir}/Project`;
        fs.mkdirSync(projectDir);
        fs.writeFileSync(`${projectDir}/sequence.json`, JSON.stringify(this.data, null, 4));
        const zip = new AdmZip();
        zip.addLocalFolder(tempDir);
        zip.writeZip(outputFilepath);
        fs.rmSync(tempDir, { recursive: true, force: true });
    }
}

// Example usage:
// const handler = new VprHandler('example.vpr');
// handler.printProperties();
// handler.data.title = 'Modified Title';
// handler.write('modified.vpr');

7. C++ Class for .VPR Handling

This uses minizip for ZIP (assume included) and nlohmann/json for JSON parsing.

#include <iostream>
#include <fstream>
#include <string>
#include <zip.h>
#include <nlohmann/json.hpp>
#include <filesystem>

namespace fs = std::filesystem;
using json = nlohmann::json;

class VprHandler {
private:
    std::string filepath;
    json data;

public:
    VprHandler(const std::string& fp = "") : filepath(fp) {
        if (!fp.empty()) {
            read(fp);
        }
    }

    void read(const std::string& fp) {
        filepath = fp;
        zip_t* za = zip_open(fp.c_str(), 0, 'r');
        if (za) {
            int index = zip_entry_open(za, "Project/sequence.json");
            if (index == 0) {
                void* buf = nullptr;
                size_t bufsize = 0;
                zip_entry_read(za, &buf, &bufsize);
                std::string jsonStr(static_cast<char*>(buf), bufsize);
                data = json::parse(jsonStr);
                free(buf);
                zip_entry_close(za);
            } else {
                throw std::runtime_error("Project/sequence.json not found.");
            }
            zip_close(za);
        } else {
            throw std::runtime_error("Failed to open ZIP.");
        }
    }

    void printProperties() {
        if (!data.is_null()) {
            std::cout << data.dump(4) << std::endl;
        } else {
            std::cout << "No data loaded." << std::endl;
        }
    }

    void write(const std::string& outputFilepath) {
        if (data.is_null()) {
            throw std::runtime_error("No data to write.");
        }
        fs::create_directory("temp_vpr/Project");
        std::ofstream jsonFile("temp_vpr/Project/sequence.json");
        jsonFile << data.dump(4);
        jsonFile.close();

        zip_t* za = zip_open(outputFilepath.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
        if (za) {
            addToZip(za, "temp_vpr", "");
            zip_close(za);
        } else {
            throw std::runtime_error("Failed to create ZIP.");
        }
        fs::remove_all("temp_vpr");
    }

private:
    void addToZip(zip_t* za, const std::string& dir, const std::string& base) {
        for (const auto& entry : fs::directory_iterator(dir)) {
            std::string path = entry.path().string();
            std::string relPath = base + entry.path().filename().string();
            if (entry.is_directory()) {
                addToZip(za, path, relPath + "/");
            } else {
                zip_entry_open(za, relPath.c_str());
                std::ifstream file(path, std::ios::binary);
                std::vector<char> buffer(std::istreambuf_iterator<char>(file), {});
                zip_entry_write(za, buffer.data(), buffer.size());
                zip_entry_close(za);
            }
        }
    }
};

// Example usage:
// int main() {
//     try {
//         VprHandler handler("example.vpr");
//         handler.printProperties();
//         handler.write("modified.vpr");
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }