Task 670: .SM File Format
Task 670: .SM File Format
.SM File Format Specifications
The .SM file format is the legacy StepMania Song File format, used for rhythm game song data and charts. It is a plain text file containing header tags for song metadata and one or more chart sections for note data. The format has been largely superseded by .SSC in newer versions of StepMania, but it is still supported. The specifications are based on the StepMania wiki and related documentation.
1. List of All Properties of This File Format Intrinsic to Its File System
The properties are the key tags in the .SM file, which are intrinsic to the format's structure. They include header properties (shared across all charts) and chart-specific properties under #NOTES. The file is text-based, so "intrinsic to its file system" likely refers to the format's internal structure and fields, not OS file system properties.
- TITLE: Primary song title.
- SUBTITLE: Song subtitle.
- ARTIST: Song artist.
- TITLETRANSLIT: Transliterated title (for non-native language display).
- SUBTITLETRANSLIT: Transliterated subtitle.
- ARTISTTRANSLIT: Transliterated artist.
- GENRE: Song genre.
- CREDIT: Simfile credit (author or pack).
- BANNER: Path to banner image.
- BACKGROUND: Path to background image.
- LYRICSPATH: Path to lyrics file (.lrc).
- CDTITLE: Path to CD title image.
- MUSIC: Path to music file.
- OFFSET: Offset in seconds for note data start.
- BPMS: BPM changes in format beat=BPM, separated by commas.
- STOPS: Stops in format beat=seconds, separated by commas.
- SAMPLESTART: Start time for music sample in seconds.
- SAMPLELENGTH: Length of music sample in seconds.
- DISPLAYBPM: Override for displayed BPM (number, range like min:max, or * for random).
- SELECTABLE: Song selectability (YES or NO).
- BGCHANGES: Background changes in format beat=file=rate=transition=effect=etc., separated by commas.
- FGCHANGES: Foreground changes (similar to BGCHANGES, but simplified).
- NOTES: Chart data, with subproperties:
- Chart type (e.g., dance-single).
- Description/author.
- Difficulty (Beginner, Easy, Medium, Hard, Challenge, Edit).
- Numerical meter.
- Groove radar values (stream, voltage, air, freeze, chaos, separated by commas).
- Note data (measures of note lines, terminated by comma or semicolon, with note symbols like 0,1,2,3,4,M,L,F,A,K,N,H for different note types).
2. Two Direct Download Links for Files of Format .SM
Direct downloads for individual .SM files are rare, as they are usually bundled in ZIP packs with music and images. Here are two links to ZIP files containing .SM files (from reputable StepMania community sites):
- https://search.stepmaniaonline.net/packs/REDs_Touhou_Eurobeat_Pad_Pack.zip (contains multiple .SM files for Touhou songs).
- https://zenius-i-vanisher.com/v5.2/downloads/simfiles/MAX-EXTREME_BG_Videos.zip (contains .SM files compatible with StepMania).
3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .SM File Dump
Here is an HTML page with embedded JavaScript that allows dragging and dropping a .SM file to parse and dump all the properties to the screen. It reads the file as text, parses the tags, and displays them in a list. ( "Ghost blog" is interpreted as a simple standalone HTML page.)
4. Python Class for .SM File
Here is a Python class that can open, read, decode (parse), write, and print all properties from a .SM file to console.
class SMFile:
def __init__(self, filepath=None):
self.properties = {}
if filepath:
self.read(filepath)
def read(self, filepath):
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
lines = content.split('\n')
current_tag = None
notes = []
for line in lines:
line = line.strip()
if line.startsWith('#'):
parts = line.split(':', 1)
if len(parts) == 2:
current_tag = parts[0][1:]
self.properties[current_tag] = parts[1].rstrip(';').strip()
elif current_tag:
self.properties[current_tag] += '\n' + line
elif line and current_tag == 'NOTES':
notes.append(line)
self.properties['NOTES'] = '\n'.join(notes)
print("File read successfully.")
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, filepath):
with open(filepath, 'w', encoding='utf-8') as f:
for key, value in self.properties.items():
f.write(f"#{key}:{value};\n")
print("File written successfully.")
# Example usage:
# sm = SMFile('example.sm')
# sm.print_properties()
# sm.write('output.sm')
5. Java Class for .SM File
Here is a Java class that can open, read, decode, write, and print all properties from a .SM file to console.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class SMFile {
private Map<String, String> properties = new LinkedHashMap<>();
public SMFile(String filepath) throws IOException {
read(filepath);
}
public void read(String filepath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line;
String currentTag = null;
StringBuilder notes = new StringBuilder();
while (line = reader.readLine() != null) {
line = line.trim();
if (line.startsWith("#")) {
String[] parts = line.split(":", 2);
if (parts.length == 2) {
currentTag = parts[0].substring(1);
properties.put(currentTag, parts[1].replaceAll(";?$", "").trim());
} else if (currentTag != null) {
properties.put(currentTag, properties.get(currentTag) + "\n" + line);
}
} else if (line.length() > 0 && "NOTES".equals(currentTag)) {
notes.append(line).append("\n");
properties.put("NOTES", notes.toString().trim());
}
}
}
System.out.println("File read successfully.");
}
public void printProperties() {
for (Map.Entry<String, String> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String filepath) throws IOException {
try (FileWriter writer = new FileWriter(filepath)) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
writer.write("#" + entry.getKey() + ":" + entry.getValue() + ";\n");
}
}
System.out.println("File written successfully.");
}
// Example usage:
// public static void main(String[] args) throws IOException {
// SMFile sm = new SMFile("example.sm");
// sm.printProperties();
// sm.write("output.sm");
// }
}
6. JavaScript Class for .SM File
Here is a JavaScript class (for Node.js) that can open, read, decode, write, and print all properties from a .SM file to console.
const fs = require('fs');
class SMFile {
constructor(filepath = null) {
this.properties = {};
if (filepath) {
this.read(filepath);
}
}
read(filepath) {
const content = fs.readFileSync(filepath, 'utf-8');
const lines = content.split('\n');
let currentTag = null;
let notes = [];
lines.forEach(line => {
line = line.trim();
if (line.startsWith('#')) {
const parts = line.split(/:(.+)/);
if (parts.length === 3) {
currentTag = parts[0].slice(1);
this.properties[currentTag] = parts[1].replace(/;$/,'').trim();
} else if (currentTag) {
this.properties[currentTag] += '\n' + line;
}
} else if (line && currentTag === 'NOTES') {
notes.push(line);
this.properties['NOTES'] = notes.join('\n');
}
});
console.log('File read successfully.');
}
printProperties() {
for (const key in this.properties) {
console.log(`${key}: ${this.properties[key]}`);
}
}
write(filepath) {
let content = '';
for (const key in this.properties) {
content += `#${key}:${this.properties[key]};\n`;
}
fs.writeFileSync(filepath, content, 'utf-8');
console.log('File written successfully.');
}
}
// Example usage:
// const sm = new SMFile('example.sm');
// sm.printProperties();
// sm.write('output.sm');
7. C Class for .SM File
Here is a C++ class that can open, read, decode, write, and print all properties from a .SM file to console.
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <sstream>
class SMFile {
private:
std::map<std::string, std::string> properties;
public:
SMFile(const std::string& filepath = "") {
if (!filepath.empty()) {
read(filepath);
}
}
void read(const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error opening file." << std::endl;
return;
}
std::string line, currentTag, notes;
while (std::getline(file, line)) {
line.erase(0, line.find_first_not_of(" \t"));
line.erase(line.find_last_not_of(" \t") + 1);
if (line.rfind("#", 0) == 0) {
size_t colonPos = line.find(':');
if (colonPos != std::string::npos) {
currentTag = line.substr(1, colonPos - 1);
std::string value = line.substr(colonPos + 1);
value.erase(value.find_last_not_of(";") + 1);
value.erase(value.find_last_not_of(" \t") + 1);
properties[currentTag] = value;
} else if (!currentTag.empty()) {
properties[currentTag] += "\n" + line;
}
} else if (!line.empty() && currentTag == "NOTES") {
notes += line + "\n";
properties["NOTES"] = notes.substr(0, notes.size() - 1);
}
}
file.close();
std::cout << "File read successfully." << std::endl;
}
void printProperties() {
for (const auto& pair : properties) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
}
void write(const std::string& filepath) {
std::ofstream file(filepath);
if (!file.is_open()) {
std::cerr << "Error opening file for writing." << std::endl;
return;
}
for (const auto& pair : properties) {
file << "#" << pair.first << ":" << pair.second << ";" << std::endl;
}
file.close();
std::cout << "File written successfully." << std::endl;
}
};
// Example usage:
// int main() {
// SMFile sm("example.sm");
// sm.printProperties();
// sm.write("output.sm");
// return 0;
// }