1 module nes.console;
2 
3 import std.base64;
4 import std.conv;
5 import std.file;
6 import std.zlib;
7 
8 import nes.apu;
9 import nes.cartridge;
10 import nes.color;
11 import nes.controller;
12 import nes.cpu;
13 import nes.filter;
14 import nes.image;
15 import nes.ines;
16 import nes.mapper;
17 import nes.palette;
18 import nes.ppu;
19 
20 class Console {
21     CPU        cpu;
22     APU        apu;
23     PPU        ppu;
24     Cartridge  cartridge;
25     Controller controller1;
26     Controller controller2;
27     Mapper     mapper;
28     ubyte[]    ram;
29 
30     this(string path) {
31         this.cartridge = LoadNESFile(path);
32 
33         this.ram = new ubyte[2048];
34         this.controller1 = new Controller();
35         this.controller2 = new Controller();
36 
37         this.mapper = NewMapper(this);
38         
39         this.cpu = new CPU(this);
40         this.apu = new APU(this);
41         this.ppu = new PPU(this);
42     }
43 
44     void reset() {
45         this.cpu.reset();
46     }
47 
48     int step() {
49         auto cpuCycles = this.cpu.step();
50         auto ppuCycles = cpuCycles * 3;
51 
52         for (auto i = 0; i < ppuCycles; i++) {
53             this.ppu.step();
54             this.mapper.step();
55         }
56 
57         for (auto i = 0; i < cpuCycles; i++) {
58             this.apu.step();
59         }
60 
61         return cpuCycles;
62     }
63 
64     int stepFrame() {
65         auto cpuCycles = 0;
66         auto frame = this.ppu.frame;
67 
68         while (frame == this.ppu.frame) {
69             cpuCycles += this.step();
70         }
71 
72         return cpuCycles;
73     }
74 
75     void stepSeconds(double seconds) {
76         auto cycles = cast(int)(CPUFrequency * seconds);
77 
78         while (cycles > 0) {
79             cycles -= this.step();
80         }
81     }
82 
83     ImageRGBA buffer() {
84         return this.ppu.front;
85     }
86 
87     RGBA backgroundColor() {
88         return Palette[this.ppu.readPalette(0) % 64];
89     }
90 
91     void setButtons1(bool[8] buttons) {
92         this.controller1.setButtons(buttons);
93     }
94 
95     void setButtons2(bool[8] buttons) {
96         this.controller2.setButtons(buttons);
97     }
98 
99     void setAudioCallback(ApuCallbackFuncType callback) {
100         this.apu.callback = callback;
101     }
102 
103     void setAudioSampleRate(double sampleRate) {
104         if (sampleRate != 0) {
105             // Convert samples per second to cpu steps per sample
106             this.apu.sampleRate = CPUFrequency / sampleRate;
107             // Initialize filters
108             this.apu.filterChain = new FilterChain(
109                 HighPassFilter(cast(float)sampleRate, 90),
110                 HighPassFilter(cast(float)sampleRate, 440),
111                 LowPassFilter(cast(float)sampleRate, 14000)
112             );
113         } else {
114             this.apu.filterChain = null;
115         }
116     }
117 
118     void saveState(string fileName) {
119         string[string] state = ["version": "1"];
120 
121         this.save(state);
122 
123         auto stateText = to!string(state);
124 
125         auto data = std.zlib.compress(cast(void[])stateText);
126 
127         write(fileName, data);
128     }
129 
130     void loadState(string fileName) {
131         auto stateData = read(fileName);
132 
133         stateData = cast(ubyte[])std.zlib.uncompress(cast(void[])stateData);
134 
135         string[string] state = to!(string[string])(cast(string)stateData);
136 
137         load(state);
138     }
139 
140     void saveBatteryBackedRam(string fileName) {
141         if (!this.cartridge.battery) return;
142 
143         auto data = std.zlib.compress(cast(void[])this.cartridge.sram);
144 
145         write(fileName, data);
146     }
147 
148     void loadBatteryBackedRam(string fileName) {
149         if (!this.cartridge.battery) return;
150 
151         auto data = read(fileName);
152 
153         this.cartridge.sram = cast(ubyte[])std.zlib.uncompress(cast(void[])data);
154     }
155 
156     private:
157         void save(string[string] state) {
158             state["console.ram"] = Base64.encode(this.ram);
159 
160             this.cpu.save(state);
161             this.apu.save(state);
162             this.ppu.save(state);
163             this.cartridge.save(state);
164             this.mapper.save(state);
165         }
166 
167         void load(string[string] state) {
168             this.ram = Base64.decode(state["console.ram"]);
169 
170             this.cpu.load(state);
171             this.apu.load(state);
172             this.ppu.load(state);
173             this.cartridge.load(state);
174             this.mapper.load(state);
175         }
176 }