You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+130-10
Original file line number
Diff line number
Diff line change
@@ -1463,7 +1463,6 @@ println!("Key for Player 1 A button: {}", config["input_player1_a"]);
1463
1463
1464
1464
This is great but the problem with this is the result is a string of the keyboard letter pressed and we need to map it to the correct `minifb` Key enum type in order to use it.
1465
1465
1466
-
1467
1466
```rust
1468
1467
letconfig=setup_config().unwrap();
1469
1468
@@ -1483,7 +1482,6 @@ let key_device_map = HashMap::from([
1483
1482
]);
1484
1483
```
1485
1484
1486
-
1487
1485
Now we can rewrite our input handling logic to look like this:
1488
1486
1489
1487
```rust
@@ -1503,7 +1501,6 @@ Now we can rewrite our input handling logic to look like this:
1503
1501
1504
1502
```
1505
1503
1506
-
1507
1504
This is great but we shouldn't expect the user to have RetroArch installed and have a valid config, and we need to support the case where they might want different settings for out frontend compared to their RetroArch, so lets set up some default values and allow users to override them if they have a file called `rustroarch.cfg`.
1508
1505
1509
1506
To do this we can refactor the `setup_config` function like so:
@@ -1551,7 +1548,7 @@ In this code first we setup some default config values, mainly for input handlin
1551
1548
1552
1549
Lets start keeping track of the size of the executable, I should have done this from the start but here we go:
1553
1550
1554
-
> Size of executable so far: 1.1MB
1551
+
> Size of executable so far: 1.1MB
1555
1552
1556
1553
# Step 20 - Saving and Loading state
1557
1554
@@ -1599,7 +1596,6 @@ But how do we know how large the buffer should be, well libRetro also has us cov
This will save the state into the current directory with the hardcoded name `save_state.state` but the problem is this same file will be overriden no matter what ROM you load, ideally it would be good to save a different file based on the game you are playing.
1619
1614
1620
-
1621
1615
After saving a file using RetroArch itself it seems to save with both the ROM name and also a save state index (which can be incremented/decremented by the user) and it also replaces any invalid characters (such as spaces) with the `_` character, this is quite a bit of logic in itself and we will need this logic in both saving and loading of state so lets create a new function for this purpose:
And we can call it similar to how we call `save_state`:
1693
1686
1694
1687
```rust
@@ -1702,7 +1695,6 @@ And we can call it similar to how we call `save_state`:
1702
1695
}
1703
1696
```
1704
1697
1705
-
1706
1698
# Step 21 - Supporting save slots
1707
1699
1708
1700
You will notice that we hard coded the `save_slot_index` to 0, we can now store the current save slot index in our global variable and then allow the user to increment and decrement the current save slot, allowing them to have different save states for the same game.
@@ -1768,5 +1760,133 @@ Now if you run the program you can incfrease and decrease the save slots and it
1768
1760
1769
1761
# Step 22 - Audio Support
1770
1762
1763
+
So far the game is playable but rather... quiet, lets change that by adding audio support!
1764
+
1765
+
We already implemented the two audio callbacks as dummy functions before to prevent the core from causing a segmentation fault but they don't do anything yet:
The first function `libretro_set_audio_sample_callback` doesn't seem to be used by the Gambate core that I am using for testing so we will need to come back to this when we find a core that requires it.
1782
+
1783
+
So `libretro_set_audio_sample_batch_callback` seems to have two paramerters, one is a data buffer that contains both the left and right audio channel dataper frame and the other is the number of frames that are in the buffer.
1784
+
1785
+
Before we can use this data we first need to figure out how we can play audio in rust across all the major Operating Systems. So after a quick google search the first resut was the `rodio` crate, so lets add it to our Cargo project:
1786
+
1787
+
```rust
1788
+
cargoaddrodio
1789
+
```
1790
+
1791
+
Now lets try to get the main example from the Rodio documentation to work:
1792
+
1793
+
```rust
1794
+
usestd::fs::File;
1795
+
usestd::io::BufReader;
1796
+
usestd::time::Duration;
1797
+
userodio::{Decoder, OutputStream, Sink};
1798
+
userodio::source::{SineWave, Source};
1799
+
1800
+
fnplay_audio() {
1801
+
let (_stream, stream_handle) =OutputStream::try_default().unwrap();
// The sound plays in a separate thread. This call will block the current thread until the sink
1809
+
// has finished playing all its queued sounds.
1810
+
sink.sleep_until_end();
1811
+
}
1812
+
```
1813
+
1814
+
Now call `play_audio` somewhere in the main game loop for example:
1815
+
1816
+
```rust
1817
+
unsafe {
1818
+
(core_api.retro_run)();
1819
+
}
1820
+
play_audio();
1821
+
```
1822
+
1823
+
If all went well you will get an annoying sound while the game is playing, but how do we convert the buffer that the callback gives us into something that rodio can play?
1824
+
1825
+
First lets add an audio_data buffer to our global variable so that we can pass it between the callback and the play_audio function:
1826
+
1827
+
```rust
1828
+
structEmulatorState {
1829
+
rom_name:String,
1830
+
core_name:String,
1831
+
frame_buffer:Option<Vec<u32>>,
1832
+
audio_data:Option<Vec<i16>>,
1833
+
```
1834
+
1835
+
Now lets update the callback so that it sets the global variables value every time its called:
If you run the program now you will notice that it starts to play audio, but in a very slow manner, turns out audio processing is very cpu time consuming.
1867
+
1868
+
You will also notice a massive dip in the frame rate, this is because we are setting up a brand new audio sink every frame, lets move this logic out ibefore the main loop and pass the Sink in to the play_audio function:
1869
+
1870
+
```rust
1871
+
letcore_api;
1872
+
let (_stream, stream_handle) =OutputStream::try_default().unwrap();
1873
+
letsink=Sink::try_new(&stream_handle).unwrap();
1874
+
```
1875
+
1876
+
Now just pass it into the call to `play_audio` like so:
1877
+
1878
+
```rust
1879
+
play_audio(&sink);
1880
+
```
1881
+
1882
+
You will notice that this has helped the frame rate a bit (around 30 fps on my machine) but its still half of what it was before we added audio support, in the next step we can sort this.
1883
+
1884
+
> Size of executable so far: 1.4MB
1885
+
1886
+
# Step 23 - Creating an Audio Thread
1887
+
1888
+
Audio processing is very cpu intensive and so far we have done all our logic in a single thread, this is now affecting the frame rate of games being played in our frontend. One solution for this is to put the audio processing in its own thread and just pass the audio data between the threads.
0 commit comments