192 | 193 |
├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── ctf_patcher.js ├── index.html ├── mk2ctf.js └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2020": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:all", 8 | "parserOptions": { 9 | "ecmaVersion": 2020, 10 | "sourceType": "module" 11 | }, 12 | "plugins": [ 13 | "html" 14 | ], 15 | "rules": { 16 | "brace-style": ["error", "1tbs", {"allowSingleLine": true}], 17 | "indent": ["error", "tab", {"ignoreComments": true}], 18 | "linebreak-style": ["error", "unix"], 19 | "quotes": ["error", "single", {"avoidEscape": true, "allowTemplateLiterals": true}], 20 | "semi": ["error", "always"], 21 | 22 | "array-bracket-newline": ["error", "consistent"], 23 | "block-spacing": ["error", "never"], 24 | "comma-dangle": ["error", "always-multiline"], 25 | "key-spacing": ["error", 26 | { 27 | "beforeColon": false, 28 | "afterColon": true, 29 | "mode": "minimum" 30 | } 31 | ], 32 | "no-fallthrough": ["error", {"commentPattern": "FALLTHRU"}], 33 | "no-mixed-operators": ["error", 34 | { 35 | "groups": [ 36 | ["&", "|", "^", "~", "<<", ">>", ">>>"], 37 | ["==", "!=", "===", "!==", ">", ">=", "<", "<="], 38 | ["&&", "||"], 39 | ["in", "instanceof"] 40 | ], 41 | "allowSamePrecedence": true 42 | } 43 | ], 44 | "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], 45 | "no-use-before-define": ["error", "nofunc"], 46 | "no-underscore-dangle": ["error", {"allowAfterThis": true}], 47 | "no-unused-vars": ["error", {"argsIgnorePattern": "^_$", "varsIgnorePattern": "^_$"}], 48 | "quote-props": ["error", "consistent"], 49 | "space-before-function-paren": ["error", { 50 | "anonymous": "never", 51 | "named": "never", 52 | "asyncArrow": "always" 53 | }], 54 | "spaced-comment": ["error", "always", { 55 | "exceptions": ["EMPTY", "FALLTHRU", "FALLTHROUGH", "NOTREACHED"] 56 | }], 57 | "wrap-iife": ["error", "inside"], 58 | "yoda": ["error", "never", {"exceptRange": true}], 59 | 60 | "no-warning-comments": "warn", 61 | 62 | "array-element-newline": "off", 63 | "capitalized-comments": "off", 64 | "complexity": "off", 65 | "func-names": "off", 66 | "func-style": "off", 67 | "function-call-argument-newline": "off", 68 | "id-length": "off", 69 | "init-declarations": "off", 70 | "line-comment-position": "off", 71 | "lines-around-comment": "off", 72 | "lines-between-class-members": "off", 73 | "max-classes-per-file": "off", 74 | "max-depth": "off", 75 | "max-len": "off", 76 | "max-lines": "off", 77 | "max-lines-per-function": "off", 78 | "max-params": "off", 79 | "max-statements": "off", 80 | "multiline-comment-style": "off", 81 | "multiline-ternary": "off", 82 | "newline-per-chained-call": "off", 83 | "no-await-in-loop": "off", 84 | "no-bitwise": "off", 85 | "no-confusing-arrow": "off", 86 | "no-console": "off", 87 | "no-continue": "off", 88 | "no-else-return": "off", 89 | "no-extra-parens": "off", 90 | "no-inline-comments": "off", 91 | "no-labels": "off", 92 | "no-lonely-if": "off", 93 | "no-magic-numbers": "off", 94 | "no-multi-spaces": "off", 95 | "no-negated-condition": "off", 96 | "no-nested-ternary": "off", 97 | "no-param-reassign": "off", 98 | "no-plusplus": "off", 99 | "no-shadow": "off", 100 | "no-sync": "off", 101 | "no-tabs": "off", 102 | "no-ternary": "off", 103 | "no-undefined": "off", 104 | "object-curly-newline": "off", 105 | "object-property-newline": "off", 106 | "one-var": "off", 107 | "operator-linebreak": "off", 108 | "padded-blocks": "off", 109 | "prefer-destructuring": "off", 110 | "prefer-named-capture-group": "off", 111 | "sort-imports": "off", 112 | "sort-keys": "off", 113 | "sort-vars": "off", 114 | "wrap-regex": "off" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 shingo45endo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sc55mk2-ctf-patcher 2 | =================== 3 | 4 | 5 | What is this? 6 | ------------- 7 | 8 | This tool modifies the firmware of SC-55mkII (or related models) to support "Alternate Voicings" (also known as Capital Tone Fallback (CTF)). 9 | 10 | 11 | What is Alternate Voicings? (a.k.a. CTF) 12 | ---------------------------------------- 13 | 14 | "Alternate Voicings" is a feature that was supported by the early GS sound modules, the SC-55 and its compatibles. This function substitutes a similar tone when a non-existent variation tone is selected. For more information, please refer to the following manuals: 15 | 16 | * [CM-300 Owner's Manual](https://web.archive.org/web/20140826121448/http://media.rolandus.com/manuals/CM-300_OM.pdf) 17 | * 4. About The GS Format 18 | * (4) Alternate Voicings - No Matter What GS Sound Source You Use, The Song Remains The Same 19 | * (5) General Use Areas and Special Use Areas 20 | * [CM-300 取扱説明書](http://lib.roland.co.jp/support/jp/manuals/res/62265292/CM-300_j.pdf) 21 | * 4. GSフォーマットについて 22 | * (4) 代理発音――どのGS音源でも同じような音色で演奏させるための工夫 23 | * (5) 汎用エリアと専用エリア 24 | 25 | But, this feature was omitted in the SC-55mkII, the successor to the SC-55. According to one theory, it infringed on Yamaha's patent and had to be removed. This feature was never restored in the many GS-compatible devices released thereafter. 26 | 27 | As a matter of fact, this feature had caused a situation in which composers could mistakenly specify a non-existent variation tone but not notice it because a capital tone would be selected automatically. These mistakes were discovered in the form of "incorrectly specified tones not being played" in the SC-55mkII and later sound modules. The disruption of music performance caused by this affected mainly PC games that use MIDI sound modules for background music. 28 | 29 | 30 | How to Use 31 | ---------- 32 | 33 | 1. Dump firmware ROM as a binary file from your SC-55mkII. 34 | 2. Apply this tool to the binary file. 35 | 3. Program the modified firmware binary to any writable ROM compatible with the original firmware ROM. 36 | 4. Replace the original firmware ROM to the newly programmed ROM. 37 | 38 | 39 | Supported Models 40 | ---------------- 41 | 42 | Note: I have not yet tried the tool on actual devices if it really works as intended. 43 | 44 | * SC-55mkII 45 | * SC-33 46 | * XP-10 47 | * PMA-5 48 | 49 | It can also be applied to the firmware of the original SC-55 and its compatible models. However, it does not make much sense. 50 | 51 | 52 | How it works 53 | ------------ 54 | 55 | The SC-55 series firmware ROM has a table of 2 bytes x 128 programs x 128 variations in the range of 0x030000-0x038000. Each value in the table indicates the serial number of the tone, and if there is no tone corresponding to the program number and variation number, 0xffff is stored. This tool rewrites the value of 0xffff in the table to the appropriate tone number to support the Alternate Voicings feature. 56 | 57 | As described in the aforementioned manual, the area of the tables to be rewritten is as follows: 58 | 59 | * Program No. 1 to 120 are processed. 60 | * No. 121 to 128 are not included because they are sound effects. 61 | * Bank select No. 0 to 63 are processed. 62 | * No. 64 to 127 are not included because they are special use area. 63 | 64 | Although not explicitly mentioned in the manual, the Alternate Voicings feature also applies to drum sets. However, the ranges of program numbers are different depending on the firmware version as follows: 65 | 66 | * v1.21 or earlier: Program No. 1 to 64 67 | * v2.00: Program No. 1 to 48 68 | 69 | As for drum sets, the firmware ROM also has a table of 1 byte x 128 programs in the range of 0x038000-0x038080. Similar to the tone tables, if there is no drum set corresponding to the program number, 0xff is stored. This tool rewrites the table according to the behavior of firmware v1.21. 70 | 71 | 72 | License 73 | ------- 74 | 75 | MIT 76 | 77 | 78 | Author 79 | ------ 80 | 81 | [shingo45endo](https://github.com/shingo45endo) 82 | -------------------------------------------------------------------------------- /ctf_patcher.js: -------------------------------------------------------------------------------- 1 | const sc55tones = { 2 | 0: { 3 | 0: 'Piano 1', 4 | 126: 'Piano 2', 5 | 127: 'Acou Piano 1', 6 | }, 7 | 1: { 8 | 0: 'Piano 2', 9 | 126: 'Piano 2', 10 | 127: 'Acou Piano 2', 11 | }, 12 | 2: { 13 | 0: 'Piano 3', 14 | 126: 'Piano 2', 15 | 127: 'Acou Piano 3', 16 | }, 17 | 3: { 18 | 0: 'Honky-tonk', 19 | 126: 'Honky-tonk', 20 | 127: 'Elec Piano 1', 21 | }, 22 | 4: { 23 | 0: 'E.Piano 1', 24 | 8: 'Detuned EP 1', 25 | 126: 'Piano 1', 26 | 127: 'Elec Piano 2', 27 | }, 28 | 5: { 29 | 0: 'E.Piano 2', 30 | 8: 'Detuned EP 2', 31 | 126: 'Piano 2', 32 | 127: 'Elec Piano 3', 33 | }, 34 | 6: { 35 | 0: 'Harpsichord', 36 | 8: 'Coupled Hps.', 37 | 126: 'Piano 2', 38 | 127: 'Elec Piano 4', 39 | }, 40 | 7: { 41 | 0: 'Clav.', 42 | 126: 'E.Piano 1', 43 | 127: 'Honkytonk', 44 | }, 45 | 8: { 46 | 0: 'Celesta', 47 | 126: 'Detuned EP 1', 48 | 127: 'Elec Org 1', 49 | }, 50 | 9: { 51 | 0: 'Glockenspiel', 52 | 126: 'E.Piano 2', 53 | 127: 'Elec Org 2', 54 | }, 55 | 10: { 56 | 0: 'Music Box', 57 | 126: 'Steel-str.Gt', 58 | 127: 'Elec Org 3', 59 | }, 60 | 11: { 61 | 0: 'Vibraphone', 62 | 126: 'Steel-str.Gt', 63 | 127: 'Elec Org 4', 64 | }, 65 | 12: { 66 | 0: 'Marimba', 67 | 126: '12-str.Gt', 68 | 127: 'Pipe Org 1', 69 | }, 70 | 13: { 71 | 0: 'Xylophone', 72 | 126: 'Funk Gt.', 73 | 127: 'Pipe Org 2', 74 | }, 75 | 14: { 76 | 0: 'Tubular-bell', 77 | 8: 'Church Bell', 78 | 126: 'Muted Gt.', 79 | 127: 'Pipe Org 3', 80 | }, 81 | 15: { 82 | 0: 'Santur', 83 | 126: 'Slap Bass 1', 84 | 127: 'Accordion', 85 | }, 86 | 16: { 87 | 0: 'Organ 1', 88 | 8: 'Detuned Or.1', 89 | 126: 'Slap Bass 1', 90 | 127: 'Harpsi 1', 91 | }, 92 | 17: { 93 | 0: 'Organ 2', 94 | 8: 'Detuned Or.2', 95 | 126: 'Slap Bass 1', 96 | 127: 'Harpsi 2', 97 | }, 98 | 18: { 99 | 0: 'Organ 3', 100 | 126: 'Slap Bass 1', 101 | 127: 'Harpsi 3', 102 | }, 103 | 19: { 104 | 0: 'Church Org.1', 105 | 8: 'Church Org.2', 106 | 126: 'Slap Bass 2', 107 | 127: 'Clavi 1', 108 | }, 109 | 20: { 110 | 0: 'Reed Organ', 111 | 126: 'Slap Bass 2', 112 | 127: 'Clavi 2', 113 | }, 114 | 21: { 115 | 0: 'Accordion Fr', 116 | 8: 'Accordion It', 117 | 126: 'Slap Bass 2', 118 | 127: 'Clavi 3', 119 | }, 120 | 22: { 121 | 0: 'Harmonica', 122 | 126: 'Slap Bass 2', 123 | 127: 'Celesta 1', 124 | }, 125 | 23: { 126 | 0: 'Bandneon', 127 | 126: 'Fingered Bs.', 128 | 127: 'Celesta 2', 129 | }, 130 | 24: { 131 | 0: 'Nylon-str.Gt', 132 | 8: 'Ukulele', 133 | 126: 'Fingered Bs.', 134 | 127: 'Syn Brass 1', 135 | }, 136 | 25: { 137 | 0: 'Steel-str.Gt', 138 | 8: '12-str.Gt', 139 | 16: 'Mandolin', 140 | 126: 'Picked Bs.', 141 | 127: 'Syn Brass 2', 142 | }, 143 | 26: { 144 | 0: 'Jazz Gt.', 145 | 8: 'Hawaiian Gt.', 146 | 126: 'Picked Bs.', 147 | 127: 'Syn Brass 3', 148 | }, 149 | 27: { 150 | 0: 'Clean Gt.', 151 | 8: 'Chorus Gt.', 152 | 126: 'Fretless Bs.', 153 | 127: 'Syn Brass 4', 154 | }, 155 | 28: { 156 | 0: 'Muted Gt.', 157 | 8: 'Funk Gt.', 158 | 126: 'Acoustic Bs.', 159 | 127: 'Syn Bass 1', 160 | }, 161 | 29: { 162 | 0: 'Overdrive Gt', 163 | 126: 'Choir Aahs', 164 | 127: 'Syn Bass 2', 165 | }, 166 | 30: { 167 | 0: 'DistortionGt', 168 | 8: 'Feedback Gt.', 169 | 126: 'Choir Aahs', 170 | 127: 'Syn Bass 3', 171 | }, 172 | 31: { 173 | 0: 'Gt.Harmonics', 174 | 8: 'Gt. Feedback', 175 | 126: 'Choir Aahs', 176 | 127: 'Syn Bass 4', 177 | }, 178 | 32: { 179 | 0: 'Acoustic Bs.', 180 | 126: 'Choir Aahs', 181 | 127: 'Fantasy', 182 | }, 183 | 33: { 184 | 0: 'Fingered Bs.', 185 | 126: 'Slow Strings', 186 | 127: 'Harmo Pan', 187 | }, 188 | 34: { 189 | 0: 'Picked Bs.', 190 | 126: 'Strings', 191 | 127: 'Chorale', 192 | }, 193 | 35: { 194 | 0: 'Fretless Bs.', 195 | 126: 'Syn.Strings3', 196 | 127: 'Glasses', 197 | }, 198 | 36: { 199 | 0: 'Slap Bass 1', 200 | 126: 'Syn.Strings3', 201 | 127: 'Soundtrack', 202 | }, 203 | 37: { 204 | 0: 'Slap Bass 2', 205 | 126: 'Organ 1', 206 | 127: 'Atmosphere', 207 | }, 208 | 38: { 209 | 0: 'Synth Bass 1', 210 | 8: 'Synth Bass 3', 211 | 126: 'Organ 1', 212 | 127: 'Warm Bell', 213 | }, 214 | 39: { 215 | 0: 'Synth Bass 2', 216 | 8: 'Synth Bass 4', 217 | 126: 'Organ 1', 218 | 127: 'Funny Vox', 219 | }, 220 | 40: { 221 | 0: 'Violin', 222 | 126: 'Organ 2', 223 | 127: 'Echo Bell', 224 | }, 225 | 41: { 226 | 0: 'Viola', 227 | 126: 'Organ 1', 228 | 127: 'Ice Rain', 229 | }, 230 | 42: { 231 | 0: 'Cello', 232 | 126: 'Organ 1', 233 | 127: 'Oboe 2001', 234 | }, 235 | 43: { 236 | 0: 'Contrabass', 237 | 126: 'Organ 2', 238 | 127: 'Echo Pan', 239 | }, 240 | 44: { 241 | 0: 'Tremolo Str', 242 | 126: 'Organ 2', 243 | 127: 'Doctor Solo', 244 | }, 245 | 45: { 246 | 0: 'PizzicatoStr', 247 | 126: 'Organ 2', 248 | 127: 'School Daze', 249 | }, 250 | 46: { 251 | 0: 'Harp', 252 | 126: 'Trumpet', 253 | 127: 'Bellsinger', 254 | }, 255 | 47: { 256 | 0: 'Timpani', 257 | 126: 'Trumpet', 258 | 127: 'Square Wave', 259 | }, 260 | 48: { 261 | 0: 'Strings', 262 | 8: 'Orchestra', 263 | 126: 'Trombone', 264 | 127: 'Str Sect 1', 265 | }, 266 | 49: { 267 | 0: 'Slow Strings', 268 | 126: 'Trombone', 269 | 127: 'Str Sect 2', 270 | }, 271 | 50: { 272 | 0: 'Syn.Strings1', 273 | 8: 'Syn.Strings3', 274 | 126: 'Trombone', 275 | 127: 'Str Sect 3', 276 | }, 277 | 51: { 278 | 0: 'Syn.Strings2', 279 | 126: 'Trombone', 280 | 127: 'Pizzicato', 281 | }, 282 | 52: { 283 | 0: 'Choir Aahs', 284 | 126: 'Trombone', 285 | 127: 'Violin 1', 286 | }, 287 | 53: { 288 | 0: 'Voice Oohs', 289 | 126: 'Trombone', 290 | 127: 'Violin 2', 291 | }, 292 | 54: { 293 | 0: 'SynVox', 294 | 126: 'Alto Sax', 295 | 127: 'Cello 1', 296 | }, 297 | 55: { 298 | 0: 'OrchestraHit', 299 | 126: 'Tenor Sax', 300 | 127: 'Cello 2', 301 | }, 302 | 56: { 303 | 0: 'Trumpet', 304 | 126: 'Baritone Sax', 305 | 127: 'Contrabass', 306 | }, 307 | 57: { 308 | 0: 'Trombone', 309 | 126: 'Alto Sax', 310 | 127: 'Harp 1', 311 | }, 312 | 58: { 313 | 0: 'Tuba', 314 | 126: 'Brass 1', 315 | 127: 'Harp 2', 316 | }, 317 | 59: { 318 | 0: 'MutedTrumpet', 319 | 126: 'Brass 1', 320 | 127: 'Guitar 1', 321 | }, 322 | 60: { 323 | 0: 'French Horn', 324 | 126: 'Brass 2', 325 | 127: 'Guitar 2', 326 | }, 327 | 61: { 328 | 0: 'Brass 1', 329 | 8: 'Brass 2', 330 | 126: 'Brass 2', 331 | 127: 'Elec Gtr 1', 332 | }, 333 | 62: { 334 | 0: 'Synth Brass1', 335 | 8: 'Synth Brass3', 336 | 126: 'Brass 1', 337 | 127: 'Elec Gtr 2', 338 | }, 339 | 63: { 340 | 0: 'Synth Brass2', 341 | 8: 'Synth Brass4', 342 | 126: 'OrchestraHit', 343 | 127: 'Sitar', 344 | }, 345 | 64: { 346 | 0: 'Soprano Sax', 347 | 127: 'Acou Bass 1', 348 | }, 349 | 65: { 350 | 0: 'Alto Sax', 351 | 127: 'Acou Bass 2', 352 | }, 353 | 66: { 354 | 0: 'Tenor Sax', 355 | 127: 'Elec Bass 1', 356 | }, 357 | 67: { 358 | 0: 'Baritone Sax', 359 | 127: 'Elec Bass 2', 360 | }, 361 | 68: { 362 | 0: 'Oboe', 363 | 127: 'Slap Bass 1', 364 | }, 365 | 69: { 366 | 0: 'English Horn', 367 | 127: 'Slap Bass 2', 368 | }, 369 | 70: { 370 | 0: 'Bassoon', 371 | 127: 'Fretless 1', 372 | }, 373 | 71: { 374 | 0: 'Clarinet', 375 | 127: 'Fretless 2', 376 | }, 377 | 72: { 378 | 0: 'Piccolo', 379 | 127: 'Flute 1', 380 | }, 381 | 73: { 382 | 0: 'Flute', 383 | 127: 'Flute 2', 384 | }, 385 | 74: { 386 | 0: 'Recorder', 387 | 127: 'Piccolo 1', 388 | }, 389 | 75: { 390 | 0: 'Pan Flute', 391 | 127: 'Piccolo 2', 392 | }, 393 | 76: { 394 | 0: 'Bottle Blow', 395 | 127: 'Recorder', 396 | }, 397 | 77: { 398 | 0: 'Shakuhachi', 399 | 127: 'Pan Pipes', 400 | }, 401 | 78: { 402 | 0: 'Whistle', 403 | 127: 'Sax 1', 404 | }, 405 | 79: { 406 | 0: 'Ocarina', 407 | 127: 'Sax 2', 408 | }, 409 | 80: { 410 | 0: 'Square Wave', 411 | 8: 'Sine Wave', 412 | 127: 'Sax 3', 413 | }, 414 | 81: { 415 | 0: 'Saw Wave', 416 | 127: 'Sax 4', 417 | }, 418 | 82: { 419 | 0: 'Syn.Calliope', 420 | 127: 'Clarinet 1', 421 | }, 422 | 83: { 423 | 0: 'Chiffer Lead', 424 | 127: 'Clarinet 2', 425 | }, 426 | 84: { 427 | 0: 'Charang', 428 | 127: 'Oboe', 429 | }, 430 | 85: { 431 | 0: 'Solo Vox', 432 | 127: 'Engl Horn', 433 | }, 434 | 86: { 435 | 0: '5th Saw Wave', 436 | 127: 'Bassoon', 437 | }, 438 | 87: { 439 | 0: 'Bass & Lead', 440 | 127: 'Harmonica', 441 | }, 442 | 88: { 443 | 0: 'Fantasia', 444 | 127: 'Trumpet 1', 445 | }, 446 | 89: { 447 | 0: 'Warm Pad', 448 | 127: 'Trumpet 2', 449 | }, 450 | 90: { 451 | 0: 'Polysynth', 452 | 127: 'Trombone 1', 453 | }, 454 | 91: { 455 | 0: 'Space Voice', 456 | 127: 'Trombone 2', 457 | }, 458 | 92: { 459 | 0: 'Bowed Glass', 460 | 127: 'Fr Horn 1', 461 | }, 462 | 93: { 463 | 0: 'Metal Pad', 464 | 127: 'Fr Horn 2', 465 | }, 466 | 94: { 467 | 0: 'Halo Pad', 468 | 127: 'Tuba', 469 | }, 470 | 95: { 471 | 0: 'Sweep Pad', 472 | 127: 'Brs Sect 1', 473 | }, 474 | 96: { 475 | 0: 'Ice Rain', 476 | 127: 'Brs Sect 2', 477 | }, 478 | 97: { 479 | 0: 'Soundtrack', 480 | 127: 'Vibe 1', 481 | }, 482 | 98: { 483 | 0: 'Crystal', 484 | 127: 'Vibe 2', 485 | }, 486 | 99: { 487 | 0: 'Atmosphere', 488 | 127: 'Syn Mallet', 489 | }, 490 | 100: { 491 | 0: 'Brightness', 492 | 127: 'Windbell', 493 | }, 494 | 101: { 495 | 0: 'Goblin', 496 | 127: 'Glock', 497 | }, 498 | 102: { 499 | 0: 'Echo Drops', 500 | 127: 'Tube Bell', 501 | }, 502 | 103: { 503 | 0: 'Star Theme', 504 | 127: 'Xylophone', 505 | }, 506 | 104: { 507 | 0: 'Sitar', 508 | 127: 'Marimba', 509 | }, 510 | 105: { 511 | 0: 'Banjo', 512 | 127: 'Koto', 513 | }, 514 | 106: { 515 | 0: 'Shamisen', 516 | 127: 'Sho', 517 | }, 518 | 107: { 519 | 0: 'Koto', 520 | 8: 'Taisho Koto', 521 | 127: 'Shakuhachi', 522 | }, 523 | 108: { 524 | 0: 'Kalimba', 525 | 127: 'Whistle 1', 526 | }, 527 | 109: { 528 | 0: 'Bag Pipe', 529 | 127: 'Whistle 2', 530 | }, 531 | 110: { 532 | 0: 'Fiddle', 533 | 127: 'Bottleblow', 534 | }, 535 | 111: { 536 | 0: 'Shanai', 537 | 127: 'Breathpipe', 538 | }, 539 | 112: { 540 | 0: 'Tinkle Bell', 541 | 127: 'Timpani', 542 | }, 543 | 113: { 544 | 0: 'Agogo', 545 | 127: 'Melodic Tom', 546 | }, 547 | 114: { 548 | 0: 'Steel Drums', 549 | 127: 'Deep Snare', 550 | }, 551 | 115: { 552 | 0: 'Woodblock', 553 | 8: 'Castanets', 554 | 127: 'Elec Perc 1', 555 | }, 556 | 116: { 557 | 0: 'Taiko', 558 | 8: 'Concert BD', 559 | 127: 'Elec Perc 2', 560 | }, 561 | 117: { 562 | 0: 'Melo. Tom 1', 563 | 8: 'Melo. Tom 2', 564 | 127: 'Taiko', 565 | }, 566 | 118: { 567 | 0: 'Synth Drum', 568 | 8: '808 Tom', 569 | 127: 'Taiko Rim', 570 | }, 571 | 119: { 572 | 0: 'Reverse Cym.', 573 | 127: 'Cymbal', 574 | }, 575 | 120: { 576 | 0: 'Gt.FretNoise', 577 | 1: 'Gt.Cut Noise', 578 | 2: 'String Slap', 579 | 127: 'Castanets', 580 | }, 581 | 121: { 582 | 0: 'Breath Noise', 583 | 1: 'Fl.Key Click', 584 | 127: 'Triangle', 585 | }, 586 | 122: { 587 | 0: 'Seashore', 588 | 1: 'Rain', 589 | 2: 'Thunder', 590 | 3: 'Wind', 591 | 4: 'Stream', 592 | 5: 'Bubble', 593 | 127: 'Orche Hit', 594 | }, 595 | 123: { 596 | 0: 'Bird', 597 | 1: 'Dog', 598 | 2: 'Horse-Gallop', 599 | 127: 'Telephone', 600 | }, 601 | 124: { 602 | 0: 'Telephone 1', 603 | 1: 'Telephone 2', 604 | 2: 'DoorCreaking', 605 | 3: 'Door', 606 | 4: 'Scratch', 607 | 5: 'Windchime', 608 | 127: 'Bird Tweet', 609 | }, 610 | 125: { 611 | 0: 'Helicopter', 612 | 1: 'Car-Engine', 613 | 2: 'Car-Stop', 614 | 3: 'Car-Pass', 615 | 4: 'Car-Crash', 616 | 5: 'Siren', 617 | 6: 'Train', 618 | 7: 'Jetplane', 619 | 8: 'Starship', 620 | 9: 'Burst Noise', 621 | 127: 'One Note Jam', 622 | }, 623 | 126: { 624 | 0: 'Applause', 625 | 1: 'Laughing', 626 | 2: 'Screaming', 627 | 3: 'Punch', 628 | 4: 'Heart Beat', 629 | 5: 'Footsteps', 630 | 127: 'Water Bell', 631 | }, 632 | 127: { 633 | 0: 'Gun Shot', 634 | 1: 'Machine Gun', 635 | 2: 'Lasergun', 636 | 3: 'Explosion', 637 | 127: 'Jungle Tune', 638 | }, 639 | }; 640 | 641 | export function modifyToneTable(allBytes, compatMode = 'sc55') { 642 | console.assert(allBytes instanceof Uint8Array); 643 | console.assert(['strict-sc55', 'sc55', 'sc55mk2'].includes(compatMode)); 644 | 645 | // Checks the size of the firmware ROM. 646 | if (allBytes.length < 0x038000) { 647 | throw new Error('The firmware ROM size is too small.'); 648 | } 649 | 650 | // Reads original tone tables. 651 | const bytes = allBytes.subarray(0x030000, 0x038000); 652 | const tableTones = convertToUint16BEArray(bytes).reduce((p, _, i, a) => { 653 | if (i % 128 === 0) { 654 | p.push(a.slice(i, i + 128)); 655 | } 656 | return p; 657 | }, []); 658 | 659 | // Defines a function to get sub-capital tone. 660 | const getSubCapitalTone = (compatMode === 'strict-sc55' || compatMode === 'sc55') ? 661 | (prog, bankM) => { 662 | const subCapitalTone = tableTones[bankM & 0x78][prog]; 663 | return (subCapitalTone !== 0xffff && !sc55tones[prog][bankM]) ? 0xffff : subCapitalTone; 664 | } : 665 | (prog, bankM) => tableTones[bankM & 0x78][prog]; 666 | 667 | // Modifies the tone tables to support "Alternate Voicings". 668 | const toneNos = []; 669 | for (let bankM = 0; bankM < 64; bankM++) { // BankM #64 and above shall not be subject to fallback because they are special use areas. 670 | const tones = convertToUint16BEArray(bytes.slice(bankM * 256, (bankM + 1) * 256)); 671 | const newTones = tones.map((toneNo, prog) => { 672 | // Prog #121 (internally #120) and above shall not be subject to fallback because they are special-effects sounds. 673 | if (prog >= 120) { 674 | return toneNo; 675 | } 676 | // If a tone exists in the tone table, returns its tone number. 677 | // But in "Strict SC-55 mode", tones not in the original SC-55 are subject to fallback. 678 | if (toneNo !== 0xffff && !(compatMode === 'strict-sc55' && !sc55tones[prog][bankM])) { 679 | return toneNo; 680 | } 681 | // Applies fallback. 682 | const subCapitalToneNo = getSubCapitalTone(prog, bankM); 683 | return (subCapitalToneNo !== 0xffff) ? subCapitalToneNo : tableTones[0][prog]; 684 | }); 685 | toneNos.push(...newTones); 686 | } 687 | 688 | // Writes back the modified tone tables. 689 | bytes.set(convertToBytes(toneNos)); 690 | } 691 | 692 | export function modifyDrumTable(allBytes, compatMode = 'sc55v1') { 693 | console.assert(allBytes instanceof Uint8Array); 694 | console.assert(['sc55v1', 'sc55v2'].includes(compatMode)); 695 | 696 | // Checks the size of the firmware ROM. 697 | if (allBytes.length < 0x038080) { 698 | throw new Error('The firmware ROM size is too small.'); 699 | } 700 | 701 | // Reads an original drum table. 702 | const tableDrumSets = allBytes.subarray(0x038000, 0x038080); 703 | 704 | // Modifies the drum table to support "Alternate Voicings". 705 | const progThreshold = (compatMode === 'sc55v1') ? 64 : 48; // "64": SC-55 v1.00-1.21 / "48": SC-55 v2.00 706 | const newTableDrumSets = tableDrumSets.map((drumSetNo, prog) => { 707 | // Prog #65 (internally #64) and above shall not be subject to fallback because they are special use areas. 708 | // In v2.00, it seems that drum sets with prog #49 (internally #48) and above are defined as having "an incredible variety of sounds". 709 | // Therefore, like special-effects sounds in tones, they also shall not be subject to fallback. 710 | if (prog >= progThreshold) { 711 | return drumSetNo; 712 | } 713 | // Applies fallback. 714 | return (drumSetNo !== 0xff) ? drumSetNo : tableDrumSets[prog & 0x78]; 715 | }); 716 | 717 | // Writes back the modified drum table. 718 | tableDrumSets.set(newTableDrumSets); 719 | } 720 | 721 | function convertToUint16BEArray(bytes) { 722 | console.assert(bytes instanceof Uint8Array && bytes.length % 2 === 0); 723 | 724 | const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); 725 | const u16s = []; 726 | for (let i = 0; i < bytes.length; i += 2) { 727 | u16s.push(view.getUint16(i, false)); 728 | } 729 | 730 | return u16s; 731 | } 732 | 733 | function convertToBytes(u16s) { 734 | console.assert(u16s?.length && u16s.every((u16) => (0 <= u16 && u16 < 0x10000))); 735 | 736 | const bytes = new Uint8Array(u16s.length * 2); 737 | const view = new DataView(bytes.buffer); 738 | for (let i = 0; i < u16s.length; i++) { 739 | view.setUint16(i * 2, u16s[i], false); 740 | } 741 | 742 | return bytes; 743 | } 744 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |