more scanline shader parameters
[zxemut.git] / emuconfig.d
blob231cb4ba18de4e164fd89e379f2aeeb66dc0d209
1 /*
2 * Copyright (c) 2017 Ketmar // Invisible Vector
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 module emuconfig;
19 private:
21 import std.net.curl : download;
23 import iv.cmdcon;
24 import iv.strex;
25 import iv.vfs;
26 import iv.zymosis;
28 import zxinfo;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public struct Config {
33 @disable this ();
34 @disable this (this);
36 static:
37 __gshared ZXTimings machine = baseTimings[0];
39 /* Stereo separation types:
40 * * ACB is used in the Melodik interface.
41 * * ABC stereo is used in the Pentagon/Scorpion.
42 * * BAC stereo does seem to exist but is quite rare:
43 * Z80Stealth emulates BAC stereo but that's about all.
44 * * CAB, BCA and CBA don't get many search results.
46 enum Stereo { None, ACB, ABC, BAC, BCA, CAB, CBA }
47 enum Speaker { TV, Beeper, Default, Flat, Crisp }
48 __gshared ushort emuSpeed = 100;
49 __gshared uint sampleRate = 48000;
50 __gshared ushort volumeBeeper = 100; // [0..400]
51 __gshared ushort volumeTape = 100; // [0..400]
52 __gshared ushort volumeAY = 100; // [0..400]; it sounds slightly better with 200 as default
53 __gshared bool sndBeeperEnabled = true;
54 __gshared bool sndTapeEnabled = false;
55 __gshared bool sndAYEnabled = true;
56 __gshared Speaker speakerType = Speaker.Default;
57 __gshared Stereo stereoType = Stereo.ABC;
58 __gshared bool floatingBus = true;
59 __gshared bool maxSpeed = false;
60 __gshared bool kbdMatrix = true; // emulate keyboard matrix effect
62 __gshared bool kempstonJ = true;
63 __gshared bool kempstonM = true;
64 __gshared bool kempstonWheel = true;
65 __gshared bool kempstonMSwapB = false; // swap buttons
67 __gshared bool rockULAIn = true; // always start with 0xff in ULA in
69 __gshared bool ayPortRead = true;
71 __gshared bool noFlic = false;
72 __gshared bool noFlicDetector = false;
75 // WD1793 config
76 static struct WD93 {
77 static:
78 __gshared string trdos128model = "pentagon"; // default 128K model for CLI TR-DOS reset
80 __gshared bool noDelay; // skip WD93 delays (instant disk)?
81 __gshared bool writeProtA; // write-protection for the given FDD (will be reset on disk clear/load)
82 __gshared bool writeProtB; // write-protection for the given FDD (will be reset on disk clear/load)
83 __gshared bool writeProtC; // write-protection for the given FDD (will be reset on disk clear/load)
84 __gshared bool writeProtD; // write-protection for the given FDD (will be reset on disk clear/load)
85 __gshared ubyte trdInterleave; // interleaving for new TR-DOS disks: [0..2]
86 __gshared bool trdAddBoot = true;
87 __gshared bool trdAddBootToSys = false;
88 __gshared bool trdTraps = false;
90 bool writeProt (uint idx) nothrow @trusted @nogc {
91 switch (idx) {
92 case 0: return writeProtA;
93 case 2: return writeProtB;
94 case 3: return writeProtC;
95 case 4: return writeProtD;
96 default:
98 return true;
101 void writeProt (uint idx, bool v) nothrow @trusted @nogc {
102 switch (idx) {
103 case 0: writeProtA = v; return;
104 case 2: writeProtB = v; return;
105 case 3: writeProtC = v; return;
106 case 4: writeProtD = v; return;
107 default:
114 // ////////////////////////////////////////////////////////////////////////// //
115 public __gshared ZymCPU emuz80; // set to valid ZymCPU instance, or emu will segfault
116 public __gshared long emuFullFrameTS; // inc with ts-per-frame by each frame
117 public __gshared ubyte zxBorderColor = 0;
120 // ////////////////////////////////////////////////////////////////////////// //
121 public struct SpeakerEqConfig {
122 int bass;
123 double treble;
126 public static immutable SpeakerEqConfig[5] speakerEqConfig = [
127 SpeakerEqConfig(200, -37.0), // TV
128 SpeakerEqConfig(1000, -67.0), // Beeper
129 SpeakerEqConfig(16, -8.0), // Default
130 SpeakerEqConfig(1, 0.0), // Flat
131 SpeakerEqConfig(1, 5.0), // Crisp
135 // ////////////////////////////////////////////////////////////////////////// //
136 public string exeDir () nothrow @trusted @nogc {
137 version(Posix) {
138 __gshared size_t dir = 0;
139 __gshared size_t dirlen = 0;
140 if (dirlen == 0) {
141 import core.stdc.stdio : snprintf;
142 import core.stdc.stdlib : malloc;
143 import core.sys.posix.unistd : readlink;
144 auto dirp = cast(char*)malloc(8194);
145 if (dirp is null) assert(0, "out of memory");
146 version(rdmd) {
147 mixin(import("zxemut_home_dir.d"));
148 dirp[0..MyHomeDir.length] = MyHomeDir[];
149 dirlen = MyHomeDir.length;
150 } else {
151 auto dl = readlink("/proc/self/exe", dirp, 8192);
152 if (dl <= 0) {
153 dirp[0] = '.';
154 dirlen = 1;
155 } else {
156 while (dl > 0 && dirp[dl-1] == '/') --dl;
157 dirlen = dl;
160 dir = cast(size_t)dirp;
162 return (cast(immutable(char)*)dir)[0..dirlen];
163 } else {
164 return ".";
169 // ////////////////////////////////////////////////////////////////////////// //
170 public string configDir () nothrow @trusted @nogc {
171 version(Posix) {
172 __gshared size_t dir = 0;
173 __gshared size_t dirlen = 0;
174 if (dirlen == 0) {
175 import core.stdc.stdio : snprintf;
176 import core.stdc.stdlib : malloc;
177 import core.sys.posix.unistd : readlink;
178 auto dirp = cast(char*)malloc(8194);
179 if (dirp is null) assert(0, "out of memory");
180 version(rdmd) {
181 mixin(import("zxemut_home_dir.d"));
182 dirp[0..MyHomeDir.length] = MyHomeDir[];
183 dirlen = MyHomeDir.length;
184 } else {
185 auto dl = readlink("/proc/self/exe", dirp, 8192);
186 if (dl <= 0) {
187 dirp[0] = '.';
188 dirlen = 1;
189 } else {
190 while (dl > 0 && dirp[dl-1] == '/') --dl;
191 dirlen = dl;
194 dir = cast(size_t)dirp;
196 return (cast(immutable(char)*)dir)[0..dirlen];
197 } else {
198 return ".";
203 // ////////////////////////////////////////////////////////////////////////// //
204 public string dataDir () nothrow @trusted @nogc {
205 version(Posix) {
206 __gshared size_t dir = 0;
207 __gshared size_t dirlen = 0;
208 if (dirlen == 0) {
209 import core.stdc.stdio : snprintf;
210 import core.stdc.stdlib : malloc;
211 import core.sys.posix.unistd : readlink;
212 auto dirp = cast(char*)malloc(8194);
213 if (dirp is null) assert(0, "out of memory");
214 version(rdmd) {
215 mixin(import("zxemut_home_dir.d"));
216 dirp[0..MyHomeDir.length] = MyHomeDir[];
217 dirlen = MyHomeDir.length;
218 } else {
219 auto dl = readlink("/proc/self/exe", dirp, 8192);
220 if (dl <= 0) {
221 dirp[0] = '.';
222 dirlen = 1;
223 } else {
224 while (dl > 0 && dirp[dl-1] == '/') --dl;
225 dirlen = dl;
228 dir = cast(size_t)dirp;
230 return (cast(immutable(char)*)dir)[0..dirlen];
231 } else {
232 return ".";
237 // ////////////////////////////////////////////////////////////////////////// //
238 public bool xfIsDiskExt (const(char)[] fn) nothrow @trusted @nogc {
239 return (fn.endsWithCI(".trd") || fn.endsWithCI(".scl") || fn.endsWithCI(".fdi") || fn.endsWithCI(".udi"));
242 public bool xfIsHoBetaExt (const(char)[] fn) nothrow @trusted @nogc {
243 return (fn.length > 3 && fn[$-2] == '$' && fn[$-3] == '.');
246 public bool xfIsSnapExt (const(char)[] fn) nothrow @trusted @nogc {
247 return (fn.endsWithCI(".sna") || fn.endsWithCI(".z80") || fn.endsWithCI(".szx"));
250 public bool xfIsTapeExt (const(char)[] fn) nothrow @trusted @nogc {
251 return (fn.endsWithCI(".tap") || fn.endsWithCI(".tzx") || fn.endsWithCI(".pzx"));
254 public bool xfIsROMExt (const(char)[] fn) nothrow @trusted @nogc {
255 return fn.endsWithCI(".rom");
258 public bool xfIsRCExt (const(char)[] fn) nothrow @trusted @nogc {
259 return fn.endsWithCI(".rc");
263 // ////////////////////////////////////////////////////////////////////////// //
264 struct OpenEx {
265 VFile snap; // snapshot
266 VFile rc; // console *.rc file
267 VFile[4] disk; // 4 possible disks
268 VFile rom; // rom file
269 VFile tape; // tape file
271 @property bool hasSnap () { return snap.isOpen; }
272 @property bool hasRC () { return rc.isOpen; }
273 @property bool hasDisk (int idx=0) { return (idx >= 0 && idx < disk.length ? disk[idx].isOpen : false); }
274 @property bool hasROM () { return rom.isOpen; }
275 @property bool hasTape () { return tape.isOpen; }
276 // just in case you will need it
277 void close () { snap.close(); rc.close(); foreach (immutable idx; 0..disk.length) disk[idx].close(); rom.close(); tape.close(); }
281 //TODO: what to do with HoBetas?
282 public OpenEx xfopenEx (const(char)[] fname, scope bool delegate (const(char)[] fname) isGoodExt) {
283 // internets
284 if (fname.startsWithCI("http://") || fname.startsWithCI("https://") || fname.startsWithCI("ftp://")) {
285 import std.file : exists, mkdirRecurse;
286 enum DestDir = "/tmp/zxemut/rundowns/";
287 mkdirRecurse(DestDir);
288 char[] dfn = new char[](fname.length+DestDir.length);
289 dfn[0..DestDir.length] = DestDir[];
290 dfn[DestDir.length..$] = fname[];
291 foreach (ref char ch; dfn[DestDir.length..$]) {
292 if (ch == '/' || ch == '\\' || ch == ':' || ch <= ' ' || ch == 127) ch = '_';
294 if (!dfn.exists) {
295 conwriteln("downloading: ", fname);
296 download(fname, cast(string)dfn); // it is safe to cast here
297 } else {
298 conwriteln("cached: ", fname);
300 return xfopenEx(dfn, isGoodExt);
302 OpenEx res;
303 // check specials
304 if (fname.startsWithCI("$EXE/")) fname = exeDir~fname[4..$].idup;
305 else if (fname.startsWithCI("$DATA/")) fname = dataDir~fname[5..$].idup;
306 else if (fname.startsWithCI("$CFG/")) fname = configDir~fname[4..$].idup;
307 // archives
308 if (fname.endsWithCI(".zip")) {
309 auto did = vfsAddPak(fname, "arc:");
310 scope(exit) vfsRemovePak(did);
311 foreach_reverse (const ref de; vfsFileList()) {
312 if (!de.name.startsWith("arc:")) continue;
313 if (isGoodExt(de.name)) {
314 if (de.name.xfIsDiskExt || de.name.xfIsHoBetaExt) {
315 foreach (immutable idx; 0..res.disk.length) {
316 if (!res.disk[idx].isOpen) { res.disk[idx] = VFile(de.name); break; }
318 } else if (de.name.xfIsSnapExt) {
319 res.snap = VFile(de.name);
320 } else if (de.name.xfIsROMExt) {
321 res.rom = VFile(de.name);
322 } else if (de.name.xfIsRCExt) {
323 res.rc = VFile(de.name);
324 } else if (de.name.xfIsTapeExt) {
325 res.tape = VFile(de.name);
329 if (res.hasSnap || res.hasDisk || res.hasROM || res.hasTape) return res;
330 throw new VFSException("no suitable file found in archive '"~fname.idup~"'");
332 // other files
333 if (isGoodExt(fname)) {
334 //TODO: load ${fname}.rc?
335 if (fname.xfIsDiskExt || fname.xfIsHoBetaExt) res.disk[0] = VFile(fname);
336 else if (fname.xfIsSnapExt) res.snap = VFile(fname);
337 else if (fname.xfIsROMExt) res.rom = VFile(fname);
338 else if (fname.xfIsTapeExt) res.tape = VFile(fname);
339 //else if (de.name.xfIsRCExt) res.rc = VFile(fname);
340 else throw new VFSException("unknown file format: '"~fname.idup~"'");
342 return res;
346 public auto xfopenEx (const(char)[] fname, scope bool function (const(char)[] fname) isGoodExt) {
347 import std.functional : toDelegate;
348 return xfopenEx(fname, isGoodExt.toDelegate);