aboutsummaryrefslogtreecommitdiff
path: root/emacs/.emacs.d/lisp/my/my-spc.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs/.emacs.d/lisp/my/my-spc.el')
-rw-r--r--emacs/.emacs.d/lisp/my/my-spc.el144
1 files changed, 144 insertions, 0 deletions
diff --git a/emacs/.emacs.d/lisp/my/my-spc.el b/emacs/.emacs.d/lisp/my/my-spc.el
new file mode 100644
index 0000000..a6ecab3
--- /dev/null
+++ b/emacs/.emacs.d/lisp/my/my-spc.el
@@ -0,0 +1,144 @@
+;;; my-spc.el -- handling spc files -*- lexical-binding: t -*-
+
+;; Copyright (C) 2023 Free Software Foundation.
+
+;; Author: Yuchen Pei <id@ypei.org>
+;; Warren Wilkinson <warrenwilkinson@gmail.com>
+;; Maintainer: Yuchen Pei <id@ypei.org>
+;; Package-Requires: ((emacs "28"))
+
+;; This file is part of dotted.
+
+;; dotted is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU Affero General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; dotted is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
+;; Public License for more details.
+
+;; You should have received a copy of the GNU Affero General Public
+;; License along with dotted. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; handling spc files.
+
+;;; Code:
+
+(require 'bindat)
+
+(defconst my-spc--id666-magic-array
+ [#x53 #x4e #x45 #x53 #x2d #x53 #x50#x43 #x37 #x30 #x30 #x20 #x53 #x6f #x75 #x6e #x64 #x20 #x46 #x69 #x6c #x65 #x20 #x44 #x61 #x74 #x61 #x20 #x76 #x30 #x2e #x33 #x30]
+ "id666 header magic pattern `SNES-SPC700 Sound File Data v0.30'")
+
+(defconst my-spc--id666-header-bindat-spec
+ '((file-identifier vec 33)
+ (eval (unless (equal last my-spc--id666-magic-array)
+ (error "id666 framing mismatch: expected `%s', got `%s'"
+ my-spc--id666-magic-array
+ last)))
+ (unused u16)
+ (has-id666 u8)
+ (revision u8)
+ (pc-reg u16)
+ (a-reg u8)
+ (x-reg u8)
+ (y-reg u8)
+ (psw-reg u8)
+ (sp-reg u8)
+ (res-reg u16)
+ (song-title strz 32)
+ (game-title strz 32)
+ (dumper strz 16)
+ (comment strz 32)
+ (date strz 11)
+ (fadeout vec 3)
+ (fadeout-length vec 5)
+ (artist strz 32))
+ "id666 header specification.
+
+Sources:
+
+- URL `https://ocremix.org/info/SPC_Format_Specification'
+- URL `https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html'")
+
+(defun my-spc--decode-id666-header (filename)
+ "Read and decode id666 header from FILENAME."
+ (with-temp-buffer
+ (set-buffer-multibyte nil)
+ (insert-file-contents-literally filename nil 0 210)
+ (bindat-unpack my-spc--id666-header-bindat-spec
+ (buffer-string))))
+
+(defun my-spc-decode-id666 (filename)
+ "Read and decode id666 metadata from FILENAME.
+Return metadata in a list of (FIELD . VALUE) cons cells, or nil
+in case of errors or if there were no known fields in FILENAME."
+ (condition-case nil
+ (let ((header (my-spc--decode-id666-header filename)))
+ (when (= 26 (bindat-get-field header 'has-id666))
+ `(
+ (title . ,(bindat-get-field header 'song-title))
+ (album . ,(bindat-get-field header 'game-title))
+ (artist . ,(bindat-get-field header 'artist))
+ (comment . ,(bindat-get-field header 'comment))
+ (dumper . ,(bindat-get-field header 'dumper))
+ (date . ,(bindat-get-field header 'date))
+ (fadeout . ,(bindat-get-field header 'fadeout))
+ (fadeout-length . ,(bindat-get-field header 'fadeout))
+ (extension . ,(file-name-extension filename))
+ (number . ,(replace-regexp-in-string
+ "[^-]+-" ""
+ (file-name-base filename)))
+ )))
+ (error nil)))
+
+(defun my-spc-format-file-name (file)
+ "Format a filename from an spc FILE.
+
+artist/album/title (note).extension"
+ (when-let* ((metadata (my-spc-decode-id666 file))
+ (artist (alist-get 'artist metadata))
+ (album (alist-get 'album metadata))
+ (title (alist-get 'title metadata))
+ (extension (alist-get 'extension metadata))
+ (number (alist-get 'number metadata)))
+ (if (or (string-empty-p artist)
+ (string-empty-p album)
+ (string-empty-p title)
+ (string-empty-p extension)
+ (string-empty-p number))
+ nil
+ (format "%s/%s/%s %s.%s" artist album number title extension))))
+
+(defun my-spc-file-rename (dir)
+ "Rename all spc files under DIR using dired."
+ (let ((pairs
+ (seq-filter
+ 'cdr
+ (mapcar
+ (lambda (file)
+ (cons (expand-file-name file)
+ (format "%s/%s"
+ (expand-file-name my-audio-incoming-dir)
+ (my-spc-format-file-name file))))
+ (directory-files-recursively dir "\\`.*\\.spc\\'")))))
+ (when (y-or-n-p
+ (print
+ (format
+ "Will do the following renaming: %s\nContinue?"
+ (string-join
+ (mapcar (lambda (pair)
+ (format "%s -> %s" (car pair) (cdr pair)))
+ pairs)
+ "\n"))))
+ (mapc
+ (lambda (pair)
+ (dired-rename-file (car pair) (cdr pair) t))
+ pairs))))
+
+(provide 'my-spc)
+;;; my-spc.el ends here