├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── beatmap.cc ├── beatmap_unix.cc ├── beatmap_win.cc ├── build.sh ├── build_test.sh ├── cflags.sh ├── common.hh ├── diff_calc.cc ├── download_test.sh ├── gentest.py ├── java-oppai ├── .gitignore ├── Example.java ├── README.md ├── doc │ ├── Beatmap.md │ ├── Buffer.md │ ├── DiffCalc.md │ ├── Mods.md │ ├── OppaiCtx.md │ └── PPCalc.md └── src │ ├── Beatmap.cpp │ ├── Buffer.cpp │ ├── DiffCalc.cpp │ ├── OppaiCtx.cpp │ ├── PPCalc.cpp │ ├── build.bat │ ├── build.cpp │ ├── build.sh │ ├── dp │ └── oppai │ │ ├── Beatmap.java │ │ ├── Buffer.java │ │ ├── DiffCalc.java │ │ ├── Mods.java │ │ ├── OppaiCtx.java │ │ ├── PPCalc.java │ │ └── package-info.java │ ├── dp_oppai_Beatmap.h │ ├── dp_oppai_Buffer.h │ ├── dp_oppai_DiffCalc.h │ ├── dp_oppai_OppaiCtx.h │ ├── dp_oppai_PPCalc.h │ └── handle.h ├── lib_example ├── amd64-msvc2010.bat ├── build.bat ├── build.sh ├── i386-msvc2010.bat └── main.cc ├── main.cc ├── math.cc ├── node-oppai ├── .npmignore ├── README.md ├── binding.gyp ├── build.sh ├── docs │ └── README.md ├── example.js ├── index.js ├── node-oppai.cc └── package.json ├── oppai.1 ├── pp_calc.cc ├── profiler.cc ├── pyoppai ├── README.md ├── example.py ├── pyoppaimodule.cc ├── python27_pyoppaimodule.cc ├── python3_pyoppaimodule.cc └── setup.py ├── release.sh ├── test.cc ├── test_suite.cc ├── types.hh └── win ├── amd64-msvc2010.bat ├── build.bat └── i386-msvc2010.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | build/ 3 | /oppai 4 | /oppai_test 5 | obj 6 | *.[oad] 7 | 8 | # Artifacts 9 | *.tar.gz 10 | *.tar.xz 11 | tags 12 | flags.log 13 | /node-oppai/oppaisrc 14 | 15 | # Project settings 16 | .ycm_extra_conf.py* 17 | 18 | # Misc 19 | *.swp 20 | *~ 21 | *.bak 22 | /oppai_cache 23 | /test_suite.json 24 | /test_suite 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | dist: precise 7 | - os: linux 8 | dist: trusty 9 | - os: osx 10 | 11 | 12 | install: true 13 | cache: 14 | directories: 15 | - test_suite 16 | 17 | script: 18 | - ./build.sh 19 | - ./download_test.sh 20 | - ./build_test.sh 21 | - ./oppai_test 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Francesco149/oppai.svg?branch=master)](https://travis-ci.org/Francesco149/oppai) 2 | 3 | # NOTE 4 | oppai has been rewritten from scratch in pure C89 here: 5 | [here](https://github.com/Francesco149/oppai-ng). it's much smaller 6 | and faster than legacy oppai. 7 | 8 | this is now deprecated and not actively developed anymore. 9 | 10 | # Index 11 | osu! pp advanced inspector (oppai) is a difficulty and pp calculator for osu! 12 | standard beatmaps. It works on any map, even unsubmitted ones and all you have 13 | to do is supply it with the map's .osu file. 14 | 15 | - [Getting started](#getting-started) 16 | - [Caching beatmaps](#caching-beatmaps) 17 | - [Compiling from source (Linux)](#compiling-from-source-linux) 18 | - [Compiling from source (Windows)](#compiling-from-source-windows) 19 | - [Compiling from source (OSX)](#compiling-from-source-osx) 20 | - [Library mode and bindings](#library-mode-and-bindings) 21 | 22 | # Getting started 23 | Demonstration usage video on windows and linux: 24 | [here](https://my.mixtape.moe/wasune.webm). 25 | 26 | * If you are on arch linux, you can use the AUR packages 27 | [oppai](https://aur.archlinux.org/packages/oppai/) or 28 | [oppai-git](https://aur.archlinux.org/packages/oppai-git/) maintained by 29 | ammongit. 30 | Otherwise, download the latest binaries for your OS from 31 | [here](https://github.com/Francesco149/oppai/releases), extract the archive 32 | and place the executable anywhere you like. Advanced users are free to add oppai 33 | to their PATH to use it anywhere. 34 | * Open cmd (or your favorite terminal emulator if you're on linux) and 35 | `cd /path/to/your/oppai/folder` (forward slashes might be backwards on 36 | windows) 37 | * Type `./oppai` for a list of possible parameters. 38 | 39 | UPDATE: 40 | You can now pipe beatmaps to oppai from stdin, which means that you can download 41 | a map on the fly and call oppai on it in one simple command. 42 | 43 | Some linux examples of piping: 44 | ```bash 45 | curl https://osu.ppy.sh/osu/37658 | ./oppai - 46 | curl https://osu.ppy.sh/osu/37658 | ./oppai - +HDHR 47 | curl https://osu.ppy.sh/osu/37658 | ./oppai - +HDHR 99% 600x 1m 48 | ``` 49 | 50 | Windows examples of piping (using powershell): 51 | ```powershell 52 | (New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - 53 | (New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - +HDHR 54 | (New-Object System.Net.WebClient).DownloadString("https://osu.ppy.sh/osu/37658") | ./oppai - +HDHR 99% 600x 1m 55 | ``` 56 | 57 | NOTE: to obtain the beatmap url, just open the desired map's page in your 58 | browser, click on the desired difficulty and copy the url, then replace /b/ with 59 | /osu/. 60 | 61 | If you don't feel like using the command line, you can hop on the 62 | [shigetora chat](https://www.twitch.tv/shigetora) and type 63 | `!oppai url_to_beatmap` followed by the parameters. But remember, you won't get 64 | the full output which contains much more useful info than just pp! So I 65 | recommend spending 1 minute downloading the tool and learning to use it from 66 | the command line. 67 | 68 | Examples: 69 | ``` 70 | !oppai https://osu.ppy.sh/osu/37658 71 | !oppai https://osu.ppy.sh/osu/37658 +HDHR 72 | !oppai https://osu.ppy.sh/osu/37658 +HDHR 500x 1m 73 | ``` 74 | 75 | # Caching beatmaps 76 | oppai automatically caches pre-parsed beatmaps in binary format in the 77 | "oppai\_cache" folder in the same directory as the oppai executable. 78 | 79 | This speeds up subsequent calculations for that same map by up to 25%, 80 | especially on slow CPU's. The cache filesize is about 65kb per map on average. 81 | 82 | If you have a machine with really slow I/O (networked filesystem, old HDD) or 83 | a cpu with really good single core performance, you might want to try passing 84 | ```-no-cache``` to disable caching and see if that makes it faster (it usually 85 | makes it SLOWER though). 86 | 87 | NOTE: cache files are not guaranteed to be compatible across architecture, so 88 | you can't copy your beatmap cache from a 64-bit machine to a 32-bit 89 | machine or use 32-bit and 64-bit oppai on the same cache folder. 90 | delete it or move it in such cases. 91 | 92 | # Compiling from source (Linux) 93 | ```bash 94 | git clone https://github.com/Francesco149/oppai.git 95 | cd oppai 96 | ./build.sh 97 | ``` 98 | 99 | To cross compile, you can edit the build.sh and add, for example, ```-m32``` 100 | after g++ in CXX. 101 | 102 | OpenSSL is required to build oppai on Linux. The library and header files are 103 | bundled in the package `libssl-dev` on most distributions. On Debian it can be 104 | obtained as follows: 105 | 106 | ```bash 107 | sudo apt-get install libssl-dev 108 | ``` 109 | 110 | # Compiling from source (Windows) 111 | You need to have git bash installed. The easiest way to get it is to install 112 | GitHub desktop. 113 | 114 | You will also need visual studio. Any version should do. You don't even need the 115 | IDE, you can even get the stand-alone compiler without all the bloat. 116 | 117 | Open git bash and type: 118 | 119 | ```bash 120 | git clone https://github.com/Francesco149/oppai.git 121 | ``` 122 | 123 | Now open a visual studio command prompt: 124 | ```bash 125 | cd \path\to\oppai\win 126 | build.bat 127 | ``` 128 | 129 | The executable will be found in the build directory. 130 | 131 | # Compiling from source (OSX) 132 | Via homebrew: 133 | ```bash 134 | brew install --HEAD pmrowla/homebrew-tap/oppai 135 | ``` 136 | Note that installing with ```--HEAD``` is not required, but it is recommended 137 | since the tap may not always be up to date with the current oppai release 138 | tarball. Installing from homebrew will place the executable in your homebrew 139 | path. 140 | 141 | Compiling from source in OSX will still require the use of homebrew since Apple 142 | no longer bundles OpenSSL headers with OSX. Also note that homebrew may give 143 | warnings about not symlinking OpenSSL, this is intended behavior and you should 144 | not ```brew link --force openssl``` due to potential conflicts with the Apple 145 | bundled OpenSSL. 146 | ```bash 147 | brew install openssl 148 | git clone https://github.com/Francesco149/oppai.git 149 | cd oppai 150 | ./build.sh 151 | ``` 152 | 153 | # Library mode and bindings 154 | oppai can now be compiled in library mode by defining OPPAI_LIB=1. this allows 155 | you to ```#include /path/to/oppai/code/main.cc``` in any C++ program and call 156 | oppai functions to parse beatmaps, calculate pp and difficulty in your own 157 | software. The API is thread safe, so you can process beatmaps in parallel if 158 | you wish so. 159 | 160 | This also makes it simple to create bindings to use oppai in pretty much any 161 | programming language. 162 | 163 | Currently available bindings: 164 | * pyoppai (Python): https://github.com/Francesco149/oppai/tree/master/pyoppai 165 | * node-oppai (node.js): https://github.com/Francesco149/oppai/tree/master/node-oppai 166 | 167 | The library mode API is still new and nothing is set in stone, but I don't see 168 | it changing in the future. 169 | 170 | Here's a minimal example of using oppai in lib mode (no error checking): 171 | 172 | ```c++ 173 | #include "../main.cc" 174 | 175 | // don't forget to define OPPAI_LIB=1 in your build script! 176 | 177 | #define BUFSIZE 2000000 178 | static char buf[BUFSIZE]; 179 | 180 | int main(int argc, char* argv[]) 181 | { 182 | if (argc != 2) { 183 | fprintf(stderr, "Usage: %s file.osu\n", argv[0]); 184 | return 1; 185 | } 186 | 187 | // if you need to multithread, create one ctx and buffer for each thread 188 | oppai_ctx ctx; 189 | 190 | beatmap b(&ctx); 191 | beatmap::parse(argv[1], b, buf, BUFSIZE, true); 192 | 193 | // b.apply_mods(mods::...) 194 | 195 | f64 stars, aim = 0, speed = 0; 196 | 197 | d_calc_ctx dctx(&ctx); 198 | stars = d_calc(&dctx, b, &aim, &speed); 199 | 200 | printf( 201 | "%.17g stars, %.17g aim stars, %.17g speed stars\n", 202 | stars, aim, speed 203 | ); 204 | 205 | pp_calc_result result = pp_calc(&ctx, aim, speed, b /* mods ... */); 206 | printf("%.17g pp\n", result.pp); 207 | 208 | return 0; 209 | } 210 | ``` 211 | 212 | Recommended compiler flags for linux: 213 | 214 | ```bash 215 | ${CXX:-g++} \ 216 | -std=c++98 \ 217 | -pedantic \ 218 | -O2 \ 219 | -Wno-variadic-macros \ 220 | -Wall -Werror \ 221 | main.cc \ 222 | -lm -lstdc++ \ 223 | -lcrypto \ 224 | -DOPPAI_LIB=1 \ 225 | -o test 226 | ``` 227 | 228 | ... and windows: 229 | ```batch 230 | cl -D_CRT_SECURE_NO_WARNINGS=1 ^ 231 | -DNOMINMAX=1 ^ 232 | -DOPPAI_LIB=1 ^ 233 | -O2 ^ 234 | -nologo -MT -Gm- -GR- -EHsc -W4 -WX ^ 235 | -wd4201 ^ 236 | -wd4100 ^ 237 | -wd4458 ^ 238 | -F8000000 ^ 239 | main.cc ^ 240 | -Fetest.exe ^ 241 | Advapi32.lib 242 | ``` 243 | 244 | See lib_example/main.cc for a more complete example with error checking. 245 | -------------------------------------------------------------------------------- /beatmap_unix.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #define mkdir(x) mkdir(x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) 6 | 7 | #ifdef __APPLE__ 8 | #include 9 | #endif 10 | 11 | internalfn 12 | size_t get_exe_path(char* buf, size_t bufsize) 13 | { 14 | #ifdef __APPLE__ 15 | uint32_t size = bufsize; 16 | 17 | if (_NSGetExecutablePath(buf, &size) == 0) 18 | { 19 | return (size_t)size; 20 | } 21 | #else 22 | ssize_t res; 23 | 24 | res = readlink("/proc/self/exe", buf, bufsize); 25 | if (res >= 0) { 26 | return (size_t)res; 27 | } 28 | 29 | res = readlink("/proc/curproc/file", buf, bufsize); 30 | if (res >= 0) { 31 | return (size_t)res; 32 | } 33 | 34 | res = readlink("/proc/self/path/a.out", buf, bufsize); 35 | if (res >= 0) { 36 | return (size_t)res; 37 | } 38 | #endif 39 | 40 | perror("readlink"); 41 | strcpy(buf, "."); 42 | 43 | return 1; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /beatmap_win.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define mkdir _mkdir 5 | 6 | internalfn 7 | size_t get_exe_path(char* buf, size_t bufsize) { 8 | return GetModuleFileNameA(0, buf, (u32)bufsize); 9 | } 10 | 11 | #define MD5_DIGEST_LENGTH 16 12 | 13 | internalfn 14 | void MD5(u8 const* buf, size_t buflen, u8* digest) 15 | { 16 | HCRYPTPROV prov; 17 | 18 | if (!CryptAcquireContext(&prov, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) 19 | { 20 | fprintf(stderr, "CryptAcquireContext 0x%08X\n", GetLastError()); 21 | return; 22 | } 23 | 24 | HCRYPTHASH hash; 25 | 26 | if (!CryptCreateHash(prov, CALG_MD5, 0, 0, &hash)) 27 | { 28 | fprintf(stderr, "CryptCreateHash 0x%08X\n", GetLastError()); 29 | goto cleanup; 30 | } 31 | 32 | if (!CryptHashData(hash, buf, (DWORD)buflen, 0)) 33 | { 34 | fprintf(stderr, "CryptHashData 0x%08X\n", GetLastError()); 35 | goto cleanup2; 36 | } 37 | 38 | DWORD hashlen = MD5_DIGEST_LENGTH; 39 | 40 | if (!CryptGetHashParam(hash, HP_HASHVAL, digest, &hashlen, 0)) 41 | { 42 | fprintf(stderr, "CryptGetHashParam 0x%08X\n", GetLastError()); 43 | } 44 | 45 | cleanup2: 46 | CryptDestroyHash(hash); 47 | 48 | cleanup: 49 | CryptReleaseContext(prov, 0); 50 | } 51 | 52 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname $0) 4 | 5 | . "$dir"/cflags.sh 6 | ldflags="-lcrypto $ldflags" 7 | 8 | if [ $(uname) = "Darwin" ] 9 | then 10 | brew_prefix=$(brew --prefix) 11 | cflags="$cflags -I$brew_prefix/opt/openssl/include" 12 | cflags="$cflags -L$brew_prefix/opt/openssl/lib" 13 | fi 14 | 15 | $cxx $cflags "$@" "$dir"/main.cc $ldflags -o oppai 16 | 17 | -------------------------------------------------------------------------------- /build_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname $0) 4 | 5 | . "$dir"/cflags.sh 6 | ldflags="-lcrypto -lcurl $ldflags" 7 | 8 | if [ $(uname) = "Darwin" ] 9 | then 10 | brew_prefix=$(brew --prefix) 11 | cflags="$cflags -I$brew_prefix/opt/openssl/include" 12 | cflags="$cflags -L$brew_prefix/opt/openssl/lib" 13 | fi 14 | 15 | $cxx $cflags "$@" "$dir"/test.cc $ldflags -o oppai_test 16 | 17 | -------------------------------------------------------------------------------- /cflags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cflags="-std=c++98 -pedantic" 4 | cflags="$cflags -O3" 5 | cflags="$cflags -Wno-variadic-macros -Wno-long-long -Wall" 6 | cflags="$cflags -ffunction-sections -fdata-sections" 7 | cflags="$cflags -g0 -fno-unwind-tables -s" 8 | cflags="$cflags -fno-asynchronous-unwind-tables" 9 | 10 | ldflags="-lm -lstdc++" 11 | 12 | cflags="$cflags $CXXFLAGS" 13 | ldflags="$ldflags $LDFLAGS" 14 | 15 | cxx=$CXX 16 | 17 | if [ $(uname) = "Darwin" ]; then 18 | cxx=${cxx:-clang++} 19 | else 20 | cxx=${cxx:-g++} 21 | fi 22 | 23 | ( 24 | uname -a 25 | echo $cxx 26 | echo $cflags 27 | echo $ldflags 28 | $cxx --version 29 | ) \ 30 | > flags.log 31 | 32 | export cflags="$cflags" 33 | export ldflags="$ldflags" 34 | export cxx="$cxx" 35 | 36 | -------------------------------------------------------------------------------- /common.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define internalfn static 4 | #define globvar static 5 | 6 | #if defined(_WIN32) || defined(_WIN64) 7 | #define NEEDS_TO_INSTALL_GENTOO 1 8 | #define strtok_r strtok_s 9 | #define __func__ __FUNCTION__ 10 | #endif 11 | 12 | #if _DEBUG 13 | #define print_caller_info() printf("%s:%d %s: ", __FILE__, __LINE__, __func__) 14 | #define dbgputs(x) print_caller_info(); puts(x) 15 | #define dbgprintf print_caller_info(); printf 16 | #else 17 | #define dbgputs(x) 18 | //internal inline void dbgprintf(char const* fmt, ...) {} 19 | #define dbgprintf(fmt, ...) 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /diff_calc.cc: -------------------------------------------------------------------------------- 1 | #include // std::greater 2 | #include 3 | 4 | #define macro_round(x) std::floor((x) + 0.5) 5 | 6 | // based on tom94's osu!tp aimod 7 | // TODO: rewrite this to be less object oriented 8 | 9 | // how much strains decay per interval (if the previous interval's peak 10 | // strains after applying decay are still higher than the current one's, 11 | // they will be used as the peak strains). 12 | const f64 decay_base[] = { 0.3, 0.15 }; 13 | 14 | // almost the normalized circle diameter (104px) 15 | const f64 almost_diameter = 90; 16 | 17 | // arbitrary tresholds to determine when a stream is spaced enough that is 18 | // becomes hard to alternate. 19 | const f64 stream_spacing = 110; 20 | const f64 single_spacing = 125; 21 | 22 | // used to keep speed and aim balanced between eachother 23 | const f64 weight_scaling[] = { 1400, 26.25 }; 24 | 25 | // non-normalized diameter where the circlesize buff starts 26 | const f32 circlesize_buff_treshold = 30; 27 | 28 | namespace diff 29 | { 30 | const u8 speed = 0, 31 | aim = 1; 32 | } 33 | 34 | // diffcalc hit object 35 | struct d_obj 36 | { 37 | oppai_ctx* ctx; 38 | hit_object* ho; 39 | 40 | f64 strains[2]; 41 | 42 | // start/end positions normalized on radius 43 | v2f norm_start; 44 | v2f norm_end; 45 | 46 | // initialized after claulcating strains 47 | bool is_single; 48 | 49 | d_obj() : is_single(false) 50 | { 51 | // strains start at 1 52 | strains[0] = 1; 53 | strains[1] = 1; 54 | } 55 | 56 | void init(oppai_ctx* ctx, hit_object* base_object, f32 radius) 57 | { 58 | this->ctx = ctx; 59 | this->ho = base_object; 60 | 61 | // positions are normalized on circle radius so that we can calc as 62 | // if everything was the same circlesize 63 | f32 scaling_factor = 52.0f / radius; 64 | 65 | // cs buff (credits to osuElements, I have confirmed that this is 66 | // indeed accurate) 67 | if (radius < circlesize_buff_treshold) 68 | { 69 | scaling_factor *= 1.f + 70 | std::min((circlesize_buff_treshold - radius), 5.f) / 50.f; 71 | } 72 | 73 | norm_start = ho->pos * scaling_factor; 74 | 75 | norm_end = norm_start; 76 | // ignoring slider lengths doesn't seem to affect star rating too 77 | // much and speeds up the calculation exponentially 78 | } 79 | 80 | void calculate_strains(d_obj& prev) 81 | { 82 | calculate_strain(prev, diff::speed); 83 | if (oppai_err(ctx)) { 84 | return; 85 | } 86 | 87 | calculate_strain(prev, diff::aim); 88 | } 89 | 90 | void calculate_strain(d_obj& prev, u8 dtype) 91 | { 92 | f64 res = 0; 93 | i32 time_elapsed = ho->time - prev.ho->time; 94 | f64 decay = pow(decay_base[dtype], time_elapsed / 1000.0); 95 | f64 scaling = weight_scaling[dtype]; 96 | 97 | switch (ho->type) 98 | { 99 | case obj::slider: // we don't use sliders in this implementation 100 | case obj::circle: 101 | res = spacing_weight(distance(prev), dtype) * scaling; 102 | break; 103 | 104 | case obj::spinner: 105 | break; 106 | 107 | case obj::invalid: 108 | die(ctx, "Found invalid hit object"); 109 | return; 110 | } 111 | 112 | res /= std::max(time_elapsed, (i32)50); 113 | strains[dtype] = prev.strains[dtype] * decay + res; 114 | } 115 | 116 | f64 spacing_weight(f64 distance, u8 diff_type) 117 | { 118 | switch (diff_type) 119 | { 120 | case diff::speed: 121 | if (distance > single_spacing) { 122 | is_single = true; 123 | return 2.5; 124 | } 125 | else if (distance > stream_spacing) { 126 | return 1.6 + 0.9 * 127 | (distance - stream_spacing) / 128 | (single_spacing - stream_spacing); 129 | } 130 | else if (distance > almost_diameter) { 131 | return 1.2 + 0.4 * (distance - almost_diameter) 132 | / (stream_spacing - almost_diameter); 133 | } 134 | else if (distance > almost_diameter / 2.0) { 135 | return 0.95 + 0.25 * 136 | (distance - almost_diameter / 2.0) / 137 | (almost_diameter / 2.0); 138 | } 139 | return 0.95; 140 | 141 | case diff::aim: 142 | return pow(distance, 0.99); 143 | 144 | default: 145 | return 0.0; 146 | } 147 | } 148 | 149 | f64 distance(d_obj& prev) { 150 | return (norm_start - prev.norm_end).len(); 151 | } 152 | }; 153 | 154 | const f64 star_scaling_factor = 0.0675; 155 | const f64 extreme_scaling_factor = 0.5; 156 | const f32 playfield_width = 512.f; // in osu!pixels 157 | 158 | // strains are calculated by analyzing the map in chunks and then taking the 159 | // peak strains in each chunk. 160 | // this is the length of a strain interval in milliseconds. 161 | const i32 strain_step = 400; 162 | 163 | // max strains are weighted from highest to lowest, and this is how much the 164 | // weight decays. 165 | const f64 decay_weight = 0.9; 166 | 167 | struct d_calc_ctx 168 | { 169 | oppai_ctx* octx; 170 | d_obj objects[beatmap::max_objects]; 171 | size_t num_objects; 172 | 173 | d_calc_ctx(oppai_ctx* octx) : octx(octx) {} 174 | }; 175 | 176 | internalfn 177 | f64 calculate_difficulty(d_calc_ctx* ctx, u8 type) 178 | { 179 | std::vector highest_strains; 180 | i32 interval_end = strain_step; 181 | f64 max_strain = 0.0; 182 | 183 | d_obj* prev = 0; 184 | 185 | for (size_t i = 0; i < ctx->num_objects; i++) 186 | { 187 | d_obj& o = ctx->objects[i]; 188 | 189 | // make previous peak strain decay until the current object 190 | while (o.ho->time > interval_end) 191 | { 192 | highest_strains.push_back(max_strain); 193 | 194 | if (!prev) { 195 | max_strain = 0.0; 196 | } else { 197 | f64 decay = pow(decay_base[type], 198 | (interval_end - prev->ho->time) / 1000.0); 199 | max_strain = prev->strains[type] * decay; 200 | } 201 | 202 | interval_end += strain_step; 203 | } 204 | 205 | // calculate max strain for this interval 206 | max_strain = std::max(max_strain, o.strains[type]); 207 | prev = &o; 208 | } 209 | 210 | f64 difficulty = 0; 211 | f64 weight = 1.0; 212 | 213 | // sort strains from greatest to lowest 214 | std::sort(highest_strains.begin(), highest_strains.end(), 215 | std::greater()); 216 | 217 | // weigh the top strains 218 | for (std::vector::iterator it = highest_strains.begin(); 219 | it != highest_strains.end(); 220 | ++it) 221 | { 222 | difficulty += weight * *it; 223 | weight *= decay_weight; 224 | } 225 | 226 | return difficulty; 227 | } 228 | 229 | // calculates overall aim, speed and stars for a map. 230 | // 231 | // aim, speed: pointers to the variables where 232 | // aim and speed stars will be stored. 233 | // 234 | // rhythm_awkwardness: optional pointer that will store the experimental 235 | // rhythm awkwardness stat. 236 | // 237 | // nsingles: optional pointer that will store the number of aim singletaps. 238 | // aim singletaps are singletaps as seen by the difficulty calculator, 239 | // computed based on spacing thresholds. 240 | // 241 | // nsingles_timing: optional pointer that will store the number of timing 242 | // singletaps (1/2 or slower notes). 243 | // 244 | // nsingles_threshold: optional pointer that will store the number of notes 245 | // equal or slower than 1/2 notes at singletap_threshold ms 246 | // 247 | // returns star rating 248 | 249 | OPPAIAPI 250 | f64 d_calc( 251 | d_calc_ctx* ctx, 252 | beatmap& b, f64* aim, f64* speed, 253 | f64* rhythm_awkwardness = 0, 254 | u16* nsingles = 0, 255 | u16* nsingles_timing = 0, 256 | u16* nsingles_threshold = 0, 257 | i32 singletap_threshold = 125) 258 | { 259 | dbgputs("\ndiff calc"); 260 | 261 | if (b.mode != 0) { 262 | die(ctx->octx, "This gamemode is not supported"); 263 | return 0; 264 | } 265 | 266 | f32 circle_radius = (playfield_width / 16.f) * (1.f - 0.7f * 267 | ((f32)b.cs - 5.f) / 5.f); 268 | 269 | dbgprintf("circle radius: %g\n", circle_radius); 270 | 271 | ctx->num_objects = b.num_objects; 272 | dbgputs("initializing objects"); 273 | 274 | for (size_t i = 0; i < b.num_objects; i++) 275 | { 276 | ctx->objects[i].init(ctx->octx, &b.objects[i], circle_radius); 277 | if (oppai_err(ctx->octx)) { 278 | return 0; 279 | } 280 | } 281 | 282 | // TODO: don't use vector 283 | std::vector intervals; 284 | 285 | d_obj* prev = &ctx->objects[0]; 286 | for (size_t i = 1; i < b.num_objects; i++) 287 | { 288 | d_obj& o = ctx->objects[i]; 289 | 290 | o.calculate_strains(*prev); 291 | if (oppai_err(ctx->octx)) { 292 | return 0; 293 | } 294 | 295 | dbgprintf("%" fi32 ": type %" fi32 ", strains %g %g, " 296 | "norm pos %s-%s, pos %s\n", 297 | o.ho->time, (int)o.ho->type, o.strains[0], o.strains[1], 298 | o.norm_start.str(), o.norm_end.str(), o.ho->pos.str()); 299 | 300 | i32 interval = o.ho->time - prev->ho->time; 301 | intervals.push_back(interval); 302 | 303 | if (nsingles && o.is_single) { 304 | ++*nsingles; 305 | } 306 | 307 | i32 one_half_threshold = (i32)( 308 | b.parent_timing(b.timing(o.ho->time))->ms_per_beat / 2 309 | ); 310 | 311 | if (o.ho->type == obj::circle || o.ho->type == obj::slider) 312 | { 313 | if (nsingles_timing && interval >= one_half_threshold) { 314 | ++*nsingles_timing; 315 | } 316 | 317 | if (nsingles_threshold && interval >= singletap_threshold) { 318 | ++*nsingles_threshold; 319 | } 320 | } 321 | 322 | prev = &o; 323 | } 324 | 325 | std::vector group; 326 | 327 | u32 noffsets = 0; 328 | 329 | if (!rhythm_awkwardness) { 330 | goto skip_awkwardness; 331 | } 332 | 333 | *rhythm_awkwardness = 0; 334 | for (size_t i = 0; i < intervals.size(); ++i) 335 | { 336 | // TODO: actually compute break time length for the map's AR 337 | bool isbreak = intervals[i] >= 1200; 338 | 339 | if (!isbreak) { 340 | group.push_back(intervals[i]); 341 | } 342 | 343 | if (isbreak || group.size() >= 5 || i == intervals.size() - 1) 344 | { 345 | for (size_t j = 0; j < group.size(); ++j) 346 | { 347 | for (size_t k = 1; k < group.size(); ++k) 348 | { 349 | if (k == j) { 350 | continue; 351 | } 352 | 353 | f64 ratio = group[j] > group[k] ? 354 | (f64)group[j] / (f64)group[k] : 355 | (f64)group[k] / (f64)group[j]; 356 | 357 | f64 closest_pot = pow(2, macro_round(log(ratio)/log(2.0))); 358 | 359 | f64 offset = std::abs(closest_pot - ratio); 360 | offset /= closest_pot; 361 | 362 | *rhythm_awkwardness += offset * offset; 363 | 364 | ++noffsets; 365 | } 366 | } 367 | 368 | group.clear(); 369 | } 370 | } 371 | 372 | *rhythm_awkwardness /= noffsets; 373 | *rhythm_awkwardness *= 82; 374 | 375 | skip_awkwardness: 376 | *aim = calculate_difficulty(ctx, diff::aim); 377 | *speed = calculate_difficulty(ctx, diff::speed); 378 | 379 | *aim = sqrt(*aim) * star_scaling_factor; 380 | *speed = sqrt(*speed) * star_scaling_factor; 381 | 382 | f64 stars = *aim + *speed + 383 | std::abs(*speed - *aim) * extreme_scaling_factor; 384 | 385 | return stars; 386 | } 387 | 388 | -------------------------------------------------------------------------------- /download_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $(find test_suite -name *.osu 2>/dev/null | wc -l) = "0" ] 4 | then 5 | wget http://www.hnng.moe/stuff/test_suite_20170811.tar.xz \ 6 | || exit 1 7 | tar xf test_suite_20170811.tar.xz || exit 1 8 | else 9 | echo "using existing test_suite" 10 | fi 11 | -------------------------------------------------------------------------------- /gentest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | import time 6 | import json 7 | import traceback 8 | import argparse 9 | import hashlib 10 | 11 | ''' hack to force utf-8 ''' 12 | reload(sys) 13 | sys.setdefaultencoding("utf-8") 14 | 15 | try: 16 | import httplib 17 | except ImportError: 18 | import http.client as httplib 19 | 20 | try: 21 | import urllib 22 | except ImportError: 23 | import urllib.parse as urllib 24 | 25 | ''' ----------------------------------------------------------- ''' 26 | 27 | parser = argparse.ArgumentParser( 28 | description = ( 29 | "generates the oppai test suite. outputs c++ code to " + 30 | "stdout and the json dump to a file." 31 | ) 32 | ) 33 | 34 | parser.add_argument( 35 | "-key", 36 | default = None, 37 | help = ( 38 | "osu! api key. required if -input-file is not present. " + 39 | "can also be specified through the OSU_API_KEY " + 40 | "environment variable" 41 | ) 42 | ) 43 | 44 | parser.add_argument( 45 | "-output-file", 46 | default = "test_suite.json", 47 | help = "dumps json to this file" 48 | ) 49 | 50 | parser.add_argument( 51 | "-input-file", 52 | default = None, 53 | help = ( 54 | "loads test suite from this json file instead of " 55 | "fetching it from osu api. if set to '-', json will be " 56 | "read from standard input" 57 | ) 58 | ) 59 | 60 | args = parser.parse_args() 61 | 62 | if args.key == None and "OSU_API_KEY" in os.environ: 63 | args.key = os.environ["OSU_API_KEY"] 64 | 65 | ''' ----------------------------------------------------------- ''' 66 | 67 | osu_treset = time.time() + 60 68 | osu_ncalls = 0 69 | 70 | def osu_get(conn, endpoint, paramsdict=None): 71 | ''' 72 | GETs /api/endpoint?paramsdict&k=args.key from conn 73 | return json object, exits process on api errors 74 | ''' 75 | global osu_treset, osu_ncalls, args 76 | 77 | sys.stderr.write("%s %s\n" % (endpoint, str(paramsdict))) 78 | 79 | paramsdict["k"] = args.key 80 | path = "/api/%s?%s" % (endpoint, urllib.urlencode(paramsdict)) 81 | 82 | while True: 83 | while True: 84 | if time.time() >= osu_treset: 85 | osu_ncalls = 0 86 | osu_treset = time.time() + 60 87 | sys.stderr.write("\napi ready\n") 88 | 89 | if osu_ncalls < 60: 90 | break 91 | else: 92 | sys.stderr.write("waiting for api cooldown...\r") 93 | time.sleep(1) 94 | 95 | 96 | try: 97 | conn.request("GET", path) 98 | osu_ncalls += 1 99 | r = conn.getresponse() 100 | 101 | raw = "" 102 | 103 | while True: 104 | try: 105 | raw += r.read() 106 | break 107 | except httplib.IncompleteRead as e: 108 | raw += e.partial 109 | 110 | j = json.loads(raw) 111 | 112 | if "error" in j: 113 | sys.stderr.write("%s\n" % j["error"]) 114 | sys.exit(1) 115 | 116 | return j 117 | 118 | except (httplib.HTTPException, ValueError) as e: 119 | sys.stderr.write("%s\n" % (traceback.format_exc())) 120 | 121 | try: 122 | ''' 123 | prevents exceptions on next request if the 124 | response wasn't previously read due to errors 125 | ''' 126 | conn.getresponse().read() 127 | 128 | except httplib.HTTPException: 129 | pass 130 | 131 | time.sleep(5) 132 | 133 | 134 | def gen_modstr(bitmask): 135 | ''' generates c++ code for a mod combination's bitmask ''' 136 | mods = [] 137 | 138 | allmods = { 139 | (1<< 0, "nf"), (1<< 1, "ez"), (1<< 3, "hd"), 140 | (1<< 4, "hr"), (1<< 6, "dt"), (1<< 8, "ht"), 141 | (1<< 9, "nc"), (1<<10, "fl"), (1<<12, "so") 142 | } 143 | 144 | for bit, string in allmods: 145 | if bitmask & bit != 0: 146 | mods.append(string) 147 | 148 | if len(mods) == 0: 149 | return "nomod" 150 | 151 | return " | ".join(mods) 152 | 153 | ''' ----------------------------------------------------------- ''' 154 | 155 | if args.key == None: 156 | sys.stderr.write( 157 | "please set OSU_API_KEY or pass it as a parameter\n" 158 | ) 159 | sys.exit(1) 160 | 161 | 162 | scores = [] 163 | 164 | if args.input_file == None: 165 | ''' fetch a fresh test suite from osu api ''' 166 | top_players = [ 167 | 124493, 4787150, 2558286, 1777162, 2831793, 50265 168 | ] 169 | 170 | osu = httplib.HTTPSConnection("osu.ppy.sh") 171 | 172 | for u in top_players: 173 | params = { "u": u, "limit": 100, "type": "id" } 174 | scores += osu_get(osu, "get_user_best", params) 175 | 176 | params = { "m": 0, "since": "2015-11-26" } 177 | maps = osu_get(osu, "get_beatmaps", params) 178 | 179 | for m in maps: 180 | params = { "b": m["beatmap_id"] } 181 | map_scores = osu_get(osu, "get_scores", params) 182 | 183 | if len(map_scores) == 0: 184 | sys.stderr.write("W: map has no scores???\n") 185 | continue 186 | 187 | ''' 188 | note: api also returns qualified and loved, so ignore 189 | maps that don't have pp in rankings 190 | ''' 191 | if not "pp" in map_scores[0]: 192 | sys.stderr.write("W: ignoring loved/qualified map\n") 193 | continue 194 | 195 | for s in map_scores: 196 | s["beatmap_id"] = m["beatmap_id"] 197 | 198 | scores += map_scores 199 | 200 | 201 | with open(args.output_file, "w+") as f: 202 | f.write(json.dumps(scores)) 203 | 204 | else: 205 | ''' load existing test suite from json file ''' 206 | with open(args.input_file, "r") as f: 207 | scores = json.loads(f.read()) 208 | 209 | 210 | print("/* this code was automatically generated by gentest.py */") 211 | print("") 212 | 213 | ''' make code a little nicer by shortening mods ''' 214 | allmods = { 215 | "nf", "ez", "hd", "hr", "dt", "ht", "nc", "fl", "so", "nomod" 216 | } 217 | 218 | for mod in allmods: 219 | print("#define {0} mods::{0}".format(mod)) 220 | 221 | print(''' 222 | struct score 223 | { 224 | uint32_t id; 225 | uint16_t max_combo; 226 | uint16_t n300, n100, n50, nmiss; 227 | uint32_t mods; 228 | double pp; 229 | }; 230 | 231 | struct score suite[] = 232 | {''') 233 | 234 | seen_hashes = [] 235 | 236 | for s in scores: 237 | ''' why is every value returned by osu api a string? ''' 238 | line = ( 239 | " { %s, %s, %s, %s, %s, %s, %s, %s }," % 240 | ( 241 | s["beatmap_id"], s["maxcombo"], s["count300"], 242 | s["count100"], s["count50"], s["countmiss"], 243 | gen_modstr(int(s["enabled_mods"])), s["pp"] 244 | ) 245 | ) 246 | 247 | ''' don't include identical scores by different people ''' 248 | s = hashlib.sha1(line).digest() 249 | if s in seen_hashes: 250 | continue 251 | 252 | print(line) 253 | seen_hashes.append(s) 254 | 255 | print("};\n") 256 | 257 | for mod in allmods: 258 | print("#undef %s" % (mod)) 259 | 260 | -------------------------------------------------------------------------------- /java-oppai/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | 3 | .classpath 4 | .project 5 | /src/*.obj 6 | /src/*.exp 7 | /src/*.dll 8 | /src/*.so 9 | /src/oppai_cache 10 | /oppai_cache 11 | *.class 12 | 13 | /src/*.jar 14 | /src/*.lib -------------------------------------------------------------------------------- /java-oppai/Example.java: -------------------------------------------------------------------------------- 1 | import dp.oppai.*; 2 | public class Example { 3 | public static boolean checkError(OppaiCtx ctx) { 4 | // Oppai doesn't throw an exception when errors occur, but gives you the errors as strings, similar to what windows or linux for example do 5 | // Feel free to make your own Exception and handle this however you like. 6 | String err = ctx.getLastErr(); 7 | 8 | if(!err.equals("")) { 9 | System.out.println(err); 10 | return true; 11 | } 12 | return false; 13 | } 14 | 15 | public static void printDiff(double stars, double aim, double speed) { 16 | System.out.printf("\n%.2f stars\n%.2f aim stars\n%.2f speed stars", stars, aim, speed); 17 | } 18 | 19 | public static void printPP(double acc, double pp, double aimPP, double speedPP, double accPP) { 20 | System.out.printf("\n%.2f aim\n%.2f speed\n%.2f acc\n%.2f pp\nfor %.2f%%", aimPP, speedPP, accPP, pp, acc ); 21 | } 22 | 23 | public static void main(String[] args) { 24 | if(args.length != 1) { 25 | System.out.println("Usage: java -Djava.library.path=\"path/to/shared/lib;${env_var:PATH}\" -cp '.:path/to/jar' path/to/main file.osu"); 26 | return; 27 | } 28 | 29 | // If you need to multithread, create one ctx and buffer for each thread 30 | OppaiCtx ctx = new OppaiCtx(); 31 | 32 | // Parse beatmap ------------------------------------------------------------ 33 | Beatmap b = new Beatmap(ctx); 34 | 35 | int BUFFER_SIZE = 8000000; // Should be big enough to hold the .osu file 36 | Buffer buf = new Buffer(BUFFER_SIZE); 37 | 38 | b.parse(args[0], buf, false, System.getProperty("user.dir")); // Don't disable caching and use the current working directory for caching 39 | 40 | if(checkError(ctx)) 41 | return; 42 | 43 | // Dispose of our buffer, since we no longer need it. If we want to eventually parse a new beatmap we should create a new buffer. 44 | // Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) 45 | // so this method is needed and it's very recommended to call it if you don't want memory leaks. 46 | // the buf object itself still exists in this scope and only the native one no longer exists, so make sure you don't pass this object to any function 47 | // anymore because the underlying native object will not exist and this will crash your program. 48 | buf.dispose(); 49 | 50 | System.out.println("Cache foler: " + System.getProperty("user.dir")); 51 | 52 | float cs = b.getCS(), od = b.getOD(), ar = b.getAR(), hp = b.getHP(); 53 | 54 | System.out.println(b); // Prints a nicely formatted message of the overall metadata of the beatmap 55 | 56 | // diff calc ---------------------------------------------------------------- 57 | 58 | OppaiCtx dctx = new OppaiCtx(ctx); // Passing a ctx object will create a diff ctx object 59 | DiffCalc dCalcer = new DiffCalc(); 60 | dCalcer.diffCalc(b, dctx, true, true, true, true); 61 | 62 | if(checkError(ctx)) 63 | return; 64 | 65 | printDiff(dCalcer.getStars(), dCalcer.getAim(), dCalcer.getSpeed()); 66 | 67 | // pp calc ------------------------------------------------------------------ 68 | PPCalc ppCalcer = new PPCalc(); 69 | ppCalcer.calcPP(b, ctx, dCalcer.getAim(), dCalcer.getSpeed()); 70 | 71 | if(checkError(ctx)) 72 | return; 73 | 74 | printPP(ppCalcer.getAccPercent(), ppCalcer.getPp(), ppCalcer.getAimPP(), ppCalcer.getSpeedPP(), ppCalcer.getAccPP()); 75 | 76 | // pp calc (with acc %) ----------------------------------------------------- 77 | ppCalcer.calcPPAcc(b, ctx, dCalcer.getAim(), dCalcer.getSpeed(), 90.0); 78 | 79 | if(checkError(ctx)) 80 | return; 81 | 82 | printPP(ppCalcer.getAccPercent(), ppCalcer.getPp(), ppCalcer.getAimPP(), ppCalcer.getSpeedPP(), ppCalcer.getAccPP()); 83 | 84 | // override OD example ------------------------------------------------------ 85 | System.out.println("\n----\nIf the map was od10:"); 86 | b.setOD(10); 87 | 88 | ppCalcer.calcPP(b, ctx, dCalcer.getAim(), dCalcer.getSpeed()); 89 | 90 | if(checkError(ctx)) 91 | return; 92 | 93 | printPP(ppCalcer.getAccPercent(), ppCalcer.getPp(), ppCalcer.getAimPP(), ppCalcer.getSpeedPP(), ppCalcer.getAccPP()); 94 | 95 | b.setOD(od); 96 | 97 | // override AR example ------------------------------------------------------ 98 | System.out.println("\n----\nIf the map was ar11:"); 99 | b.setAR(11); 100 | 101 | ppCalcer.calcPP(b, ctx, dCalcer.getAim(), dCalcer.getSpeed()); 102 | 103 | if(checkError(ctx)) 104 | return; 105 | 106 | printPP(ppCalcer.getAccPercent(), ppCalcer.getPp(), ppCalcer.getAimPP(), ppCalcer.getSpeedPP(), ppCalcer.getAccPP()); 107 | 108 | b.setAR(ar); 109 | // override CS example ------------------------------------------------------ 110 | System.out.println("\n----\nIf the map was cs6.5:"); 111 | b.setCS(6.5f); 112 | 113 | // remember that CS is map-changing so difficulty must be recomputed 114 | dCalcer.diffCalc(b, dctx, true, true, true, true); 115 | 116 | if(checkError(ctx)) 117 | return; 118 | 119 | printDiff(dCalcer.getStars(), dCalcer.getAim(), dCalcer.getSpeed()); 120 | 121 | ppCalcer.calcPP(b, ctx, dCalcer.getAim(), dCalcer.getSpeed()); 122 | 123 | if(checkError(ctx)) 124 | return; 125 | 126 | printPP(ppCalcer.getAccPercent(), ppCalcer.getPp(), ppCalcer.getAimPP(), ppCalcer.getSpeedPP(), ppCalcer.getAccPP()); 127 | 128 | b.setCS(cs); 129 | 130 | // mods example ------------------------------------------------------------- 131 | System.out.println("\n----\nWith HDHR:"); 132 | 133 | // mods are a bitmask, same as what the osu! api uses 134 | int mods = Mods.HD | Mods.HR; 135 | b.applyMods(mods); 136 | 137 | // Some mods (like HR) are map-changing, so we need to recompute the diff 138 | dCalcer.diffCalc(b, dctx, true, true, true, true); 139 | 140 | if(checkError(ctx)) 141 | return; 142 | 143 | printDiff(dCalcer.getStars(), dCalcer.getAim(), dCalcer.getSpeed()); 144 | 145 | ppCalcer.calcPP(b, ctx, dCalcer.getAim(), dCalcer.getSpeed()); 146 | 147 | if(checkError(ctx)) 148 | return; 149 | 150 | printPP(ppCalcer.getAccPercent(), ppCalcer.getPp(), ppCalcer.getAimPP(), ppCalcer.getSpeedPP(), ppCalcer.getAccPP()); 151 | 152 | // Dispose of our contexts and beatmap, since we no longer needed. 153 | b.dispose(); 154 | ctx.dispose(); 155 | dctx.dispose(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /java-oppai/README.md: -------------------------------------------------------------------------------- 1 | Java bindings for oppai. Tested on windows and linux. 2 | 3 | # What is this and why should I use it 4 | Bindings are much cleaner and more high-performant than spawning an oppai process, 5 | especially if you need to run calculations on thousands of maps. 6 | It also makes it easier to handle errors and customize behaviour beyond what 7 | the command line tool can do. 8 | 9 | # How to install 10 | Make sure you have git to clone this repository. On linux it's usually available 11 | in your package manager if not already installed, while on windows you will need 12 | to install git bash. 13 | 14 | ## Cloning and compiling the jar 15 | 16 | To create the jar file for the library, firstly, make sure you have the jdk tools so you can use commands 17 | like javac, jar and java in your terminal. (you might have to add their location to your PATH) 18 | open up your favorite terminal and do the following: 19 | 20 | ```bash 21 | git clone https://github.com/Francesco149/oppai.git 22 | cd oppai/java-oppai/src 23 | javac dp/oppai/*.java 24 | jar cf oppai.jar dp/oppai/*.class 25 | ``` 26 | This will create the jar file that you will refer to in your own project, containing all of the 27 | needed classes. 28 | 29 | ## Compiling the shared library 30 | 31 | Since these are bindings for code that was written in C++, JNI had to be used 32 | to talk to native code. JNI relies on shared libraries, so we'll have to make one. 33 | 34 | ### For Windows 35 | 36 | You will need the MSVC build tools for this step, if you don't have them, 37 | dl them [here](http://landinghub.visualstudio.com/visual-cpp-build-tools) 38 | (oppai was not made to be built on mingw) 39 | Now comes the part where you need to check whether the java version you are using is 40 | 32 or 64 bits, since you will have to compile the DLL accordingly. 41 | 42 | You will have to target the compiler's platform to be the same as your java's platform (i.e 32 bit or 64 bit) 43 | Open up a developer command prompt (if you don't have this, download visual studio to install it) 44 | and follow the guide [here](https://msdn.microsoft.com/en-us/library/f2ccy3wt.aspx) to set the compiler's 45 | target platform to java's target platform. (NOTE: this will only be set for this particular command prompt and ocne you close and reopen it, 46 | what you set will be reset back) 47 | Navigate back (in the same dev command prompt) to /oppai/java-oppai/src and run build.bat: 48 | 49 | ```bash 50 | build.bat 51 | ``` 52 | Note that the paths to \include and \include\win32 will change according to where your jdk 53 | was isntalled and whether or not it's for 64 or 32 bits (i.e Program Files(x86) for 32 and Program Files for 64) 54 | so make sure you change these paths if this is different for you using the environment variable ```CXXFLAGS``` like this: 55 | 56 | ```bash 57 | set CXXFLAGS="/I\path\to\inc /I\path\to\inc\windows" 58 | ``` 59 | 60 | ### For Linux 61 | 62 | It's quite simpler to compile on Linux since you can set your architecture and platform from the compiler flags 63 | and you don't have to dl any tools. 64 | 65 | You may have to install openSSL if you don't have it yet, take a look at compiling oppai for linux in the main README. 66 | 67 | Just run this and make sure your platform matches the java platform youre using: 68 | 69 | ```bash 70 | ./build.sh 71 | ``` 72 | You may have to change the include paths as well as add a target platform flag. (if your java is 32 bit although youre on a 64 bit system) 73 | If you need to change your include paths, run ```build.sh``` like so: 74 | 75 | ```bash 76 | CXXFLAGS="-I/path/to/include -I/path/to/include/linux" ./build.sh 77 | ``` 78 | 79 | you can also add a flag to change the target platform in ```CXXFLAGS``` 80 | 81 | ### For OSX 82 | 83 | This is untested but should work. Open an issue if you have any problems. 84 | Run the ```build.sh```: 85 | 86 | ```bash 87 | ./build.sh 88 | ``` 89 | You may have to change the include paths as well as add a target platform flag. (if your java is 32 bit although youre on a 64 bit system) 90 | If you need to change your include paths, run ```build.sh``` like so: 91 | 92 | ```bash 93 | CXXFLAGS="-I/path/to/include -I/path/to/include/linux" ./build.sh 94 | ``` 95 | 96 | # Using the library 97 | 98 | Now that you have both the shared library and the jar file in your directory, you can add them to your project 99 | by adding the jar to your classpath and the shared library to your java build path. 100 | 101 | Here is an example of running `Example.java` so you can see how to add the jar and shared library to the classpath and build path. 102 | 103 | ```bash 104 | $ javac -cp src/oppai.jar Example.java 105 | 106 | $ java -Djava.library.path=./src -cp .:src/oppai.jar Example /path/to/song.osu 107 | ``` 108 | Just add the path to the shared lib to `java.library.path` and the path to the jar file to `-cp` 109 | 110 | # Usage 111 | 112 | For a good example with proper error checking and object disposal take a look at ```Example.java``` 113 | 114 | # Documentation 115 | 116 | for the full documentation (which I suggest you to read if you plan on using this for a proper project) go to /doc 117 | 118 | Have fun! 119 | -------------------------------------------------------------------------------- /java-oppai/doc/Beatmap.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `public class Beatmap` 4 | 5 | The Beatmap class represents a beatmap struct from oppai. 6 | 7 | * **Author:** dpisdaniel 8 | 9 | 10 | ## `public Beatmap(OppaiCtx ctx)` 11 | 12 | Constructor for class Beatmap. Constructs a new beatmap with the given context. 13 | 14 | * **Parameters:** `ctx` — the context to associate the beatmap with. 15 | 16 | ## `public void parse(String path, Buffer buffer, boolean disableCache, String cachePath)` 17 | 18 | Parses a .osu file into a beatmap object. 19 | 20 | * **Parameters:** 21 | * `path` — the path to the .osu file to parse or "-" to read from stdin 22 | * `buffer` — the buffer object to use for containing the beatmap data. 23 | * `disableCache` — passing true will disable caching. Passing false will use caching. 24 | 25 | If false is given, cachePath should also be a valid path. 26 | * `cachePath` — the path to cache beatmaps at. If no valid path is given, it will use the path of the current exectuable. 27 | 28 | ## `public native float getCS()` 29 | 30 | * **Returns:** the CS of the beatmap. 31 | 32 | ## `public native float getOD()` 33 | 34 | * **Returns:** the OD of the beatmap. 35 | 36 | ## `public native float getAR()` 37 | 38 | * **Returns:** the AR of the beatmap. 39 | 40 | ## `public native float getHP()` 41 | 42 | * **Returns:** the HP of the beatmap. 43 | 44 | ## `public native void setCS(float cs)` 45 | 46 | Sets the CS of the beatmap to the given CS. 47 | 48 | * **Parameters:** `cs` — the CS to set the beatmap's CS to. 49 | 50 | ## `public native void setOD(float od)` 51 | 52 | Sets the OD of the beatmap to the given OD. 53 | 54 | * **Parameters:** `od` — the OD to set the beatmap's OD to. 55 | 56 | ## `public native void setAR(float ar)` 57 | 58 | Sets the AR of the beatmap to the given AR. 59 | 60 | * **Parameters:** `ar` — the AR to set the beatmap's AR to. 61 | 62 | ## `public native String getArtist()` 63 | 64 | * **Returns:** the artist of the beatmap. 65 | 66 | ## `public native String getTitle()` 67 | 68 | * **Returns:** the title of the beatmap. 69 | 70 | ## `public native String getVersion()` 71 | 72 | * **Returns:** the version of the beatmap. 73 | 74 | ## `public native String getCreator()` 75 | 76 | * **Returns:** the creator of the beatmap. 77 | 78 | ## `public native int getNumObjects()` 79 | 80 | * **Returns:** the number of objects in the beatmap. 81 | 82 | ## `public native int getNumCircles()` 83 | 84 | * **Returns:** the number of circles in the beatmap. 85 | 86 | ## `public native int getNumSliders()` 87 | 88 | * **Returns:** the number of sliders in the beatmap. 89 | 90 | ## `public native int getNumSpinners()` 91 | 92 | * **Returns:** the number of spinners in the beatmap. 93 | 94 | ## `public native int getMaxCombo()` 95 | 96 | * **Returns:** the max combo of the beatmap. 97 | 98 | ## `public native short getMode()` 99 | 100 | * **Returns:** the mode code of the beatmap. 101 | 102 | ## `public native void applyMods(int modMask)` 103 | 104 | Applies map-changing mods to a beatmap. 105 | 106 | * **Parameters:** `modMask` — any combination of the mod constants bit-wise OR-ed together. (e.g Mods.HD | Mods.HR) 107 | 108 | ## `public native void dispose()` 109 | 110 | Disposes of this beatmap instance. Use this when this beatmap instance is no longer needed. Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) so this method is needed. It's very recommended to call this if you don't want memory leaks. the Beatmap instance itself will still exist in the scope it was made, only the native object will no longer exist, so make sure you don't pass this instance to any function anymore because the underlying native object will not exist and this will crash your program. 111 | 112 | ## `public String toString()` 113 | 114 | Creates a string containing some of the metadata of the beatmap in a formatted way like so: False Noise - Skyshards[Shroud](by Shiirn) CS4.0 OD9.0 AR9.4 HP7.4 1168 objects (601 circles, 565 sliders, 2 spinners) max combo: 1978 115 | 116 | * **Returns:** a formatted string of the metadata of the beatmap. -------------------------------------------------------------------------------- /java-oppai/doc/Buffer.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `public class Buffer` 4 | 5 | Buffer represents a buffer used for containing all the beatmap data. 6 | 7 | * **Author:** dpisdaniel 8 | 9 | ## `public Buffer(int bufferLength)` 10 | 11 | Constructor for the Buffer class. Constructs a new buffer to hold the beatmap contents. 12 | 13 | * **Parameters:** `bufferLength` — the size in bytes of the buffer. 14 | 15 | ## `public int getBufferLength()` 16 | 17 | Retrieves the buffer's length. 18 | 19 | * **Returns:** the size (length) of the buffer in bytes. 20 | 21 | ## `public native void dispose()` 22 | 23 | Disposes of this buffer instance. Use this when this buffer instance is no longer needed. Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) so this method is needed. It's very recommended to call this if you don't want memory leaks. the Buffer instance itself will still exist in the scope it was made, only the native object will no longer exist, so make sure you don't pass this instance to any function anymore because the underlying native object will not exist and this will crash your program. -------------------------------------------------------------------------------- /java-oppai/doc/DiffCalc.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `public class DiffCalc` 4 | 5 | The DiffCalc class is used for all calculations related to the difficulty of a beatmap. 6 | 7 | * **Author:** dpisdaniel 8 | 9 | ## `public static final int DEFAULT_SINGLETAP_THRESHOLD = 125` 10 | 11 | ## `public void diffCalc(Beatmap b, OppaiCtx diffCtx, boolean withAwkwardness, boolean withAimSingles, boolean withTimingSingles, boolean withThresholdSingles, int singletapThreshold )` 12 | 13 | Calculates difficulty (star rating) for a beatmap. 14 | 15 | * **Parameters:** 16 | * `b` — the Beatmap to calculate the difficulty of. 17 | * `diffCtx` — oppai context object. 18 | * `withAwkwardness` — if True, rhythm awkwardness will be calculated, otherwise, the rhythmAwkwardness property will not be set. 19 | * `withAimSingles` — if True, the number of aim singletaps will be calculated. Aim singletaps are seen by the difficulty calculator (based on 20 | 21 | a spacing threshold). if False, the nSingles property will not be set. 22 | * `withTimingSingles` — if True, the number of 1/2 notes will be calculated. Otherwise, the nSinglesTiming property will not be set. 23 | * `withThresholdSingles` — if True, the number of notes that are 1/2 or slower at singletapThreshold ms will be calculated. Otherwise, 24 | 25 | the nSinglesThreshold property will not be set. 26 | * `singletapThreshold` — The singletap threshold in ms. Use the default one if you don't care or know what this is. Alternatively, 27 | * **See also:** Beatmap#diffCalc(OppaiCtx, boolean, boolean, boolean, boolean) 28 | 29 | ## `public void diffCalc(Beatmap b, OppaiCtx diffCtx, boolean withAwkwardness, boolean withAimSingles, boolean withTimingSingles, boolean withThresholdSingles)` 30 | 31 | Calculates difficulty (star rating) for a beatmap. 32 | 33 | * **Parameters:** 34 | * `b` — the Beatmap to calculate the difficulty of. 35 | * `diffCtx` — oppai context object. 36 | * `withAwkwardness` — if True, rhythm awkwardness will be calculated, otherwise, the rhythmAwkwardness property will not be set. 37 | * `withAimSingles` — if True, the number of aim singletaps will be calculated. Aim singletaps are seen by the difficulty calculator (based on 38 | 39 | a spacing threshold). if False, the nSingles property will not be set. 40 | * `withTimingSingles` — if True, the number of 1/2 notes will be calculated. Otherwise, the nSinglesTiming property will not be set. 41 | * `withThresholdSingles` — if True, the number of notes that are 1/2 or slower at singletapThreshold ms will be calculated. Otherwise, 42 | 43 | the nSinglesThreshold property will not be set. 44 | 45 | ## `public double getStars()` 46 | 47 | * **Returns:** the star rating of the beatmap. 48 | 49 | ## `public double getAim()` 50 | 51 | * **Returns:** the aim rating of the beatmap. 52 | 53 | ## `public double getSpeed()` 54 | 55 | * **Returns:** the speed rating of the beatmap. 56 | 57 | ## `public double getRhythmAwkwardness()` 58 | 59 | * **Returns:** the rhythm awkwardness of the beatmap. 60 | 61 | ## `public double getnSingles()` 62 | 63 | * **Returns:** the number of singles of the beatmap. 64 | 65 | ## `public double getnSinglesTiming()` 66 | 67 | * **Returns:** the singles timing of the beatmap. 68 | 69 | ## `public double getnSinglesThreshold()` 70 | 71 | * **Returns:** the singles threshold of the beatmap. -------------------------------------------------------------------------------- /java-oppai/doc/Mods.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `public final class Mods` 4 | 5 | Mods contains all the different mods constants for oppai's mods bitmask. 6 | 7 | * **Author:** dpisdaniel 8 | * **Version:** 1.0 9 | 10 | ## `public static final int NOMOD = 0` 11 | ## `public static final int NF = 1 << 0` 12 | ## `public static final int EZ = 1 << 1` 13 | ## `public static final int HD = 1 << 3` 14 | ## `public static final int HR = 1 << 4` 15 | ## `public static final int DT = 1 << 6` 16 | ## `public static final int HT = 1 << 8` 17 | ## `public static final int NC = 1 << 9` 18 | ## `public static final int FL = 1 << 10` 19 | ## `public static final int SO = 1 << 12` -------------------------------------------------------------------------------- /java-oppai/doc/OppaiCtx.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `public class OppaiCtx` 4 | 5 | OppaiCtx represents an ctx struct. It can represent either a regular context or a diff context. 6 | 7 | * **Author:** dpisdaniel 8 | 9 | ## `public OppaiCtx()` 10 | 11 | Constructor for class OppaiCtx. Constructs a new context to use with a beatmap. 12 | 13 | ## `public OppaiCtx(OppaiCtx ctx)` 14 | 15 | Constructor for class OppaiCtx. Constructs a new difficulty context to use when calculating beatmap difficulty 16 | 17 | * **Parameters:** `ctx` — - The context that is associated with 18 | 19 | ## `public long getContextHandle()` 20 | 21 | Retrieves the handle for this context. 22 | 23 | * **Returns:** the handle for this context. 24 | 25 | ## `public String getLastErr()` 26 | 27 | Retrieves the last error from this context. If no error is found, returns an empty string. 28 | 29 | * **Returns:** The last error's string. 30 | 31 | ## `public native void dispose()` 32 | 33 | Disposes of this OppaiCtx instance. Use this when this OppaiCtx instance is no longer needed. Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) so this method is needed. It's very recommended to call this if you don't want memory leaks. the OppaiCtx instance itself will still exist in the scope it was made, only the native object will no longer exist, so make sure you don't pass this instance to any function anymore because the underlying native object will not exist and this will crash your program. -------------------------------------------------------------------------------- /java-oppai/doc/PPCalc.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## `public class PPCalc` 4 | 5 | The PPCalc class is used for all calculations related to pp calculations of a beatmap. 6 | 7 | * **Author:** dpisdaniel 8 | 9 | ## `public static final short DEFAULT_COMBO = (short)0xFFFF` 10 | ## `public static final short DEFAULT_MISSES = 0` 11 | ## `public static final short DEFAULT_C300 = (short)0xFFFF` 12 | ## `public static final short DEFAULT_C100 = 0` 13 | ## `public static final short DEFAULT_C50 = 0` 14 | ## `public static final int DEFAULT_SCORE_VER = 1` 15 | ## `public static final double DEFAULT_ACCURACY = 100.0` 16 | 17 | ## `public double getAccPercent()` 18 | 19 | * **Returns:** the acc percent of the beatmap. 20 | 21 | ## `public double getPp()` 22 | 23 | * **Returns:** the pp of the beatmap given by the acc percent and applied mods. 24 | 25 | ## `public double getAimPP()` 26 | 27 | * **Returns:** the aim pp of the beatmap given by the acc percent and applied mods. 28 | 29 | ## `public double getSpeedPP()` 30 | 31 | * **Returns:** the speed pp of the beatmap given by the acc percent and applied mods. 32 | 33 | ## `public double getAccPP()` 34 | 35 | * **Returns:** the acc pp of the beatmap given by the acc percent and applied mods. 36 | 37 | ## `public void calcPP(Beatmap b, OppaiCtx ctx, double aim, double speed, int usedMods, int combo, int misses, int c300, int c100, int c50, int scoreVersion)` 38 | 39 | Calculates pp for a beatmap. Sets the results in accPercent, pp, aimPP, speedPP and accPP Use other overloads of this function if you don't want to pass some of these values. 40 | 41 | * **Parameters:** 42 | * `b` — the Beatmap to calc the pp for 43 | * `ctx` — the oppai context object 44 | * `aim` — aim stars. 45 | * `speed` — speed stars. 46 | * `usedMods` — any combination of the mod constants bit-wise OR-ed together (example: Mods.HD | Mods.HR) defaults to Mods#NOMOD 47 | * `combo` — the combo. defaults to #DEFAULT_COMBO 48 | * `misses` — the num. of misses. defaults to #DEFAULT_MISSES 49 | * `c300` — the num. of c300. defaults to #DEFAULT_C300 50 | * `c100` — the num. of c100. defaults to #DEFAULT_C100 51 | * `c50` — the num. of c50. defaults to #DEFAULT_C50 52 | * `scoreVersion` — 1 or 2. the default is 1. scorev2 affects accuracy pp 53 | 54 | ## `public void calcPP(Beatmap b, OppaiCtx ctx, double aim, double speed, int usedMods)` 55 | 56 | Overloaded function. For the default values see {link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} 57 | 58 | ## `public void calcPP(Beatmap b, OppaiCtx ctx, int aim, int speed, int usedMods, int combo, int misses, int c300, int c100, int c50, int scoreVersion)` 59 | 60 | Overloaded function. For the default values see {link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} 61 | 62 | ## `public void calcPP(Beatmap b, OppaiCtx ctx, double aim, double speed)` 63 | 64 | Overloaded function. For the default values see {link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} 65 | 66 | ## `public void calcPPAcc(Beatmap b, OppaiCtx ctx, double aim, double speed, double acc)` 67 | 68 | Overloaded function. For more options and an explanation see #calcPPAcc(Beatmap, OppaiCtx, double, double, double, int, int, int, int) 69 | 70 | * **Parameters:** 71 | * `b` — the Beatmap to calc the pp for 72 | * `ctx` — oppai ctx object see OppaiCtx#OppaiCtx() 73 | * `aim` — aim stars see DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean) 74 | * `speed` — speed stars see DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean) 75 | * `acc` — the accuracy in percentage to calculate 76 | 77 | ## `public void calcPPAcc(Beatmap b, OppaiCtx ctx, double aim, double speed, double acc, int usedMods, int combo, int misses, int scoreVersion)` 78 | 79 | Sets the results in accPercent, pp, aimPP, speedPP and accPP Same as #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int) but with percentage accuracy. 80 | 81 | * **Parameters:** 82 | * `b` — the Beatmap to calc the pp for 83 | * `ctx` — oppai ctx object see OppaiCtx#OppaiCtx() 84 | * `aim` — aim stars see DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean) 85 | * `speed` — speed speed stars see DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean) 86 | * `acc` — acc the accuracy in percentage to calculate 87 | * `usedMods` — any combination of the mod constants bit-wise OR-ed together (e.g Mods.HD | Mods.HR) defaults to Mods#NOMOD 88 | * `combo` — the combo. defaults to #DEFAULT_COMBO 89 | * `misses` — the num. of misses. defaults to #DEFAULT_MISSES 90 | * `scoreVersion` — 1 or 2. the default is 1. scorev2 affects accuracy pp -------------------------------------------------------------------------------- /java-oppai/src/Beatmap.cpp: -------------------------------------------------------------------------------- 1 | #include "dp_oppai_Beatmap.h" 2 | 3 | JNIEXPORT void JNICALL 4 | Java_dp_oppai_Beatmap_dispose(JNIEnv* env, jobject obj) { 5 | beatmap* b = getHandle(env, obj); 6 | if (!b) 7 | return; 8 | delete b; 9 | } 10 | 11 | JNIEXPORT void JNICALL 12 | Java_dp_oppai_Beatmap_newBeatmap(JNIEnv *env, jobject obj, jobject ctxObject) { 13 | oppai_ctx* ctx = getHandle(env, ctxObject); 14 | beatmap* b = new beatmap(ctx); 15 | setHandle(env, obj, b); 16 | } 17 | 18 | JNIEXPORT void JNICALL 19 | Java_dp_oppai_Beatmap_nativeParse(JNIEnv *env, jobject obj, jstring osuFilePath, jobject bufferObj, jint bufferLength, jboolean disableCaching, jstring cacheFilePath) { 20 | beatmap* b = getHandle(env, obj); 21 | char* buffer = getHandle(env, bufferObj); 22 | u32 bufLen = (u32)bufferLength; 23 | bool disableCache = (bool)disableCaching; 24 | char const* customCachePath = env->GetStringUTFChars(cacheFilePath, 0);; 25 | char const* filePath = env->GetStringUTFChars(osuFilePath, 0); 26 | 27 | beatmap::parse(filePath, *b, buffer, bufLen, disableCache, customCachePath); 28 | } 29 | 30 | JNIEXPORT jfloat JNICALL 31 | Java_dp_oppai_Beatmap_getCS(JNIEnv *env, jobject obj) { 32 | beatmap* b = getHandle(env, obj); 33 | return b->cs; 34 | } 35 | 36 | JNIEXPORT jfloat JNICALL 37 | Java_dp_oppai_Beatmap_getOD(JNIEnv* env, jobject obj) { 38 | beatmap* b = getHandle(env, obj); 39 | return b->od; 40 | } 41 | 42 | JNIEXPORT jfloat JNICALL 43 | Java_dp_oppai_Beatmap_getAR(JNIEnv* env, jobject obj) { 44 | beatmap* b = getHandle(env, obj); 45 | return b->ar; 46 | } 47 | 48 | JNIEXPORT jfloat JNICALL 49 | Java_dp_oppai_Beatmap_getHP(JNIEnv* env, jobject obj) { 50 | beatmap* b = getHandle(env, obj); 51 | return b->hp; 52 | } 53 | 54 | JNIEXPORT jstring JNICALL 55 | Java_dp_oppai_Beatmap_getArtist(JNIEnv* env, jobject obj) { 56 | beatmap* b = getHandle(env, obj); 57 | return env->NewStringUTF(b->artist); 58 | } 59 | 60 | JNIEXPORT jstring JNICALL 61 | Java_dp_oppai_Beatmap_getTitle(JNIEnv* env, jobject obj) { 62 | beatmap* b = getHandle(env, obj); 63 | return env->NewStringUTF(b->title); 64 | } 65 | 66 | JNIEXPORT jstring JNICALL 67 | Java_dp_oppai_Beatmap_getVersion(JNIEnv* env, jobject obj) { 68 | beatmap* b = getHandle(env, obj); 69 | return env->NewStringUTF(b->version); 70 | } 71 | 72 | JNIEXPORT jstring JNICALL 73 | Java_dp_oppai_Beatmap_getCreator(JNIEnv* env, jobject obj) { 74 | beatmap* b = getHandle(env, obj); 75 | return env->NewStringUTF(b->creator); 76 | } 77 | 78 | JNIEXPORT jint JNICALL 79 | Java_dp_oppai_Beatmap_getNumObjects(JNIEnv* env, jobject obj) { 80 | beatmap* b = getHandle(env, obj); 81 | return (int)b->num_objects; 82 | } 83 | 84 | JNIEXPORT jint JNICALL 85 | Java_dp_oppai_Beatmap_getNumCircles(JNIEnv* env, jobject obj) { 86 | beatmap* b = getHandle(env, obj); 87 | return (int)b->num_circles; 88 | } 89 | 90 | JNIEXPORT jint JNICALL 91 | Java_dp_oppai_Beatmap_getNumSliders(JNIEnv* env, jobject obj) { 92 | beatmap* b = getHandle(env, obj); 93 | return (int)b->num_sliders; 94 | } 95 | 96 | JNIEXPORT jint JNICALL 97 | Java_dp_oppai_Beatmap_getNumSpinners(JNIEnv* env, jobject obj) { 98 | beatmap* b = getHandle(env, obj); 99 | return (int)b->num_spinners; 100 | } 101 | 102 | JNIEXPORT jint JNICALL 103 | Java_dp_oppai_Beatmap_getMaxCombo(JNIEnv* env, jobject obj) { 104 | beatmap* b = getHandle(env, obj); 105 | return (int)b->max_combo; 106 | } 107 | 108 | JNIEXPORT jshort JNICALL 109 | Java_dp_oppai_Beatmap_getMode(JNIEnv* env, jobject obj) { 110 | beatmap* b = getHandle(env, obj); 111 | return (jshort)b->mode; 112 | } 113 | 114 | JNIEXPORT void JNICALL 115 | Java_dp_oppai_Beatmap_applyMods(JNIEnv* env, jobject obj, jint modMask) { 116 | u32 modBitMask = (u32)modMask; 117 | beatmap* b = getHandle(env, obj); 118 | b->apply_mods(modBitMask); 119 | } 120 | 121 | #define setter(name, funcName, varType, varJNIType) \ 122 | JNIEXPORT void JNICALL \ 123 | Java_dp_oppai_Beatmap_set##funcName(JNIEnv* env, jobject obj, varJNIType valToSet) { \ 124 | varType value = (varType)valToSet; \ 125 | beatmap* p = getHandle(env, obj); \ 126 | p->name = value; \ 127 | } 128 | 129 | #define fsetter(name, funcName) setter(name, funcName, f32, jfloat) 130 | 131 | fsetter(cs, CS) 132 | fsetter(od, OD) 133 | fsetter(ar, AR) 134 | -------------------------------------------------------------------------------- /java-oppai/src/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "dp_oppai_Buffer.h" 2 | #include 3 | 4 | JNIEXPORT void JNICALL 5 | Java_dp_oppai_Buffer_dispose(JNIEnv* env, jobject obj) { 6 | char* p = getHandle(env, obj); 7 | if (!p) 8 | return; 9 | free(p); 10 | } 11 | 12 | JNIEXPORT void JNICALL 13 | Java_dp_oppai_Buffer_newBuffer(JNIEnv *env, jobject obj, jint length) { 14 | char* buffer = (char*)malloc(length); 15 | setHandle(env, obj, buffer); 16 | } 17 | -------------------------------------------------------------------------------- /java-oppai/src/DiffCalc.cpp: -------------------------------------------------------------------------------- 1 | #include "dp_oppai_DiffCalc.h" 2 | 3 | JNIEXPORT void JNICALL 4 | Java_dp_oppai_DiffCalc_nativeDiffCalc(JNIEnv *env, jobject obj, jobject beatmapObj, jobject diffCtxObject, jboolean withAwkwardness, jboolean withAimSingles, jboolean withTimingSingles, jboolean withThresholdSingles, jint singletapThreshold) { 5 | int with_awkwardness = (int)withAwkwardness; 6 | int with_aim_singles = (int)withAimSingles; 7 | int with_timing_singles = (int)withTimingSingles; 8 | int with_threshold_singles = (int)withThresholdSingles; 9 | i32 singletap_threshold = (int)singletapThreshold; // The default value will be passed from singletapThreshold if the user doesn't specify otherwise 10 | d_calc_ctx* dctx = getHandle(env, diffCtxObject); 11 | beatmap* b = getHandle(env, beatmapObj); 12 | f64 aim = 0, speed = 0, rhythm_awkwardness = 0; 13 | u16 nsingles = 0, nsingles_timing = 0, nsingles_threshold = 0; 14 | 15 | f64 stars = 16 | d_calc( 17 | dctx, *b, &aim, &speed, 18 | with_awkwardness ? &rhythm_awkwardness : 0, 19 | with_aim_singles ? &nsingles : 0, 20 | with_timing_singles ? &nsingles_timing : 0, 21 | with_threshold_singles ? &nsingles_threshold : 0, 22 | singletap_threshold 23 | ); 24 | 25 | jclass c = env->GetObjectClass(obj); 26 | jfieldID fieldID; 27 | jdouble value; 28 | 29 | // This looks like shit but there is no better way..? 30 | #define setMember(val, fieldName) \ 31 | fieldID = env->GetFieldID(c, fieldName, "D"); \ 32 | value = (jdouble)val; \ 33 | env->SetDoubleField(obj, fieldID, value); 34 | 35 | setMember(stars, "stars") 36 | setMember(aim, "aim") 37 | setMember(speed, "speed") 38 | setMember(rhythm_awkwardness, "rhythmAwkwardness") 39 | setMember(nsingles, "nSingles") 40 | setMember(nsingles_timing, "nSinglesTiming") 41 | setMember(nsingles_threshold, "nSinglesThreshold") 42 | 43 | #undef setMember 44 | } -------------------------------------------------------------------------------- /java-oppai/src/OppaiCtx.cpp: -------------------------------------------------------------------------------- 1 | #include "dp_oppai_OppaiCtx.h" 2 | 3 | JNIEXPORT void JNICALL 4 | Java_dp_oppai_OppaiCtx_newCtx(JNIEnv *env, jobject obj) { 5 | oppai_ctx *ctx = new oppai_ctx(); 6 | setHandle(env, obj, ctx); 7 | } 8 | 9 | JNIEXPORT void JNICALL 10 | Java_dp_oppai_OppaiCtx_newDiffCalcContext(JNIEnv *env, jobject obj, jobject ctxObject) { 11 | oppai_ctx* ctx = getHandle(env, ctxObject); 12 | d_calc_ctx* dctx = new d_calc_ctx(ctx); 13 | setHandle(env, obj, dctx); 14 | } 15 | 16 | JNIEXPORT jstring JNICALL 17 | Java_dp_oppai_OppaiCtx_nativeGetLastErr(JNIEnv *env, jobject obj) { 18 | 19 | oppai_ctx* ctx = getHandle(env, obj); 20 | 21 | char const* error = ""; 22 | if (oppai_err(ctx)) 23 | error = oppai_err(ctx); 24 | 25 | return env->NewStringUTF(error); 26 | } 27 | 28 | JNIEXPORT void JNICALL 29 | Java_dp_oppai_OppaiCtx_dispose(JNIEnv* env, jobject obj) { 30 | oppai_ctx* ctx = getHandle(env, obj); 31 | if (!ctx) 32 | return; 33 | delete ctx; 34 | } 35 | -------------------------------------------------------------------------------- /java-oppai/src/PPCalc.cpp: -------------------------------------------------------------------------------- 1 | #include "dp_oppai_PPCalc.h" 2 | 3 | JNIEXPORT void JNICALL 4 | Java_dp_oppai_PPCalc_nativeCalcPP(JNIEnv* env, jobject obj, jobject beatmapObj, jobject ctxObject, jdouble aim, jdouble speed, jint usedMods, jint combo, jint misses, jint c300, jint c100, jint c50, jint scoreVersion) { 5 | f64 _aim = (f64)aim, _speed = (f64)speed; 6 | u32 _usedMods = (u32)usedMods; 7 | u16 _combo = (u16)combo, _misses = (u16)misses, _c300 = (u16)c300, _c100 = (u16)c100, _c50 = (u16)c50; 8 | u32 _scoreVersion = (u32)scoreVersion; 9 | 10 | oppai_ctx* ctx = getHandle(env, ctxObject); 11 | beatmap* b = getHandle(env, beatmapObj); 12 | 13 | pp_calc_result res = 14 | pp_calc( 15 | ctx, 16 | _aim, _speed, 17 | *b, 18 | _usedMods, 19 | _combo, _misses, _c300, _c100, _c50, 20 | _scoreVersion 21 | ); 22 | 23 | jclass c = env->GetObjectClass(obj); 24 | jfieldID fieldID; 25 | jdouble value; 26 | 27 | #define setMember(val, fieldName) \ 28 | fieldID = env->GetFieldID(c, fieldName, "D"); \ 29 | value = (jdouble)val; \ 30 | env->SetDoubleField(obj, fieldID, value); 31 | 32 | setMember(res.acc_percent, "accPercent") 33 | setMember(res.pp, "pp") 34 | setMember(res.aim_pp, "aimPP") 35 | setMember(res.speed_pp, "speedPP") 36 | setMember(res.acc_pp, "accPP") 37 | #undef setMember 38 | } 39 | 40 | JNIEXPORT void JNICALL 41 | Java_dp_oppai_PPCalc_nativeCalcPPAcc(JNIEnv* env, jobject obj, jobject beatmapObj, jobject ctxObject, jdouble aim, jdouble speed, jdouble acc, jint usedMods, jint combo, jint misses, jint scoreVersion) { 42 | f64 _aim = (f64)aim, 43 | _speed = (f64)speed, 44 | accPercent = (f64)acc; 45 | u32 _usedMods = (u32)usedMods, 46 | _scoreVersion = (u32)scoreVersion; 47 | u16 _combo = (u16)combo, 48 | _misses = (u16)misses; 49 | 50 | oppai_ctx* ctx = getHandle(env, ctxObject); 51 | beatmap* b = getHandle(env, beatmapObj); 52 | 53 | pp_calc_result res = 54 | pp_calc_acc( 55 | ctx, 56 | _aim, _speed, 57 | *b, 58 | accPercent, 59 | _usedMods, 60 | _combo, _misses, 61 | _scoreVersion 62 | ); 63 | 64 | jclass c = env->GetObjectClass(obj); 65 | jfieldID fieldID; 66 | jdouble value; 67 | 68 | #define setMember(val, fieldName) \ 69 | fieldID = env->GetFieldID(c, fieldName, "D"); \ 70 | value = (jdouble)val; \ 71 | env->SetDoubleField(obj, fieldID, value); 72 | 73 | setMember(res.acc_percent, "accPercent") 74 | setMember(res.pp, "pp") 75 | setMember(res.aim_pp, "aimPP") 76 | setMember(res.speed_pp, "speedPP") 77 | setMember(res.acc_pp, "accPP") 78 | #undef setMember 79 | } -------------------------------------------------------------------------------- /java-oppai/src/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | del oppai.dll 4 | del build.obj 5 | del oppai.exp 6 | del oppai.lib 7 | 8 | set CXXFLAGS=%CXXFLAGS% /I"C:\Program Files\Java\jdk1.8.0_131\include" 9 | set CXXFLAGS=%CXXFLAGS% /I"C:\Program Files\Java\jdk1.8.0_131\include\win32" 10 | cl /D_CRT_SECURE_NO_WARNINGS=1 ^ 11 | /DNOMINMAX=1 /DOPPAI_LIB=1 ^ 12 | /O2 ^ 13 | /nologo /MD /LD /GR /EHsc /W4 /WX ^ 14 | /wd4201 ^ 15 | /wd4100 ^ 16 | /wd4458 ^ 17 | /wd4800 ^ 18 | /F8000000 ^ 19 | %CXXFLAGS% ^ 20 | /Fe"oppai.dll" ^ 21 | build.cpp ^ 22 | Advapi32.lib 23 | 24 | -------------------------------------------------------------------------------- /java-oppai/src/build.cpp: -------------------------------------------------------------------------------- 1 | // This is the main file of the dll 2 | #include 3 | #include "handle.h" 4 | #include 5 | #include "../../main.cc" 6 | #include "Beatmap.cpp" 7 | #include "OppaiCtx.cpp" 8 | #include "Buffer.cpp" 9 | #include "PPCalc.cpp" 10 | #include "DiffCalc.cpp" 11 | -------------------------------------------------------------------------------- /java-oppai/src/build.sh: -------------------------------------------------------------------------------- 1 | cxxflags="$CXXFLAGS" 2 | 3 | if [ $(uname) = "Darwin" ]; then 4 | brew_prefix=$(brew --prefix) 5 | cxxflags="$cxxflags -I/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Headers" 6 | cxxflags="$cxxflags -I/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers" 7 | ${CXX:-clang++} \ 8 | -I$brew_prefix/opt/openssl/include \ 9 | -L$brew_prefix/opt/openssl/lib \ 10 | $cxxflags \ 11 | -std=c++98 \ 12 | -pedantic \ 13 | -O2 \ 14 | $@ \ 15 | -Wno-variadic-macros \ 16 | -Wall -Werror \ 17 | build.cpp \ 18 | -lm -lstdc++ \ 19 | -lcrypto \ 20 | -o liboppai.jnilib 21 | else 22 | cxxflags="$cxxflags -I/usr/lib/jvm/java-8-openjdk-amd64/include" 23 | cxxflags="$cxxflags -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux" 24 | ${CXX:-g++} \ 25 | -std=c++98 \ 26 | -fPIC \ 27 | -shared \ 28 | $cxxflags \ 29 | -pedantic \ 30 | -O2 \ 31 | $@ \ 32 | -Wno-variadic-macros \ 33 | -Wall -Werror \ 34 | build.cpp \ 35 | -lm -lstdc++ \ 36 | -lcrypto \ 37 | -o liboppai.so 38 | [ -f ./oppai ] && strip -R .comment ./oppai 39 | fi 40 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/Beatmap.java: -------------------------------------------------------------------------------- 1 | package dp.oppai; 2 | 3 | /** 4 | * The Beatmap class represents a beatmap struct from oppai. 5 | * 6 | * @author dpisdaniel 7 | * 8 | */ 9 | public class Beatmap { 10 | 11 | private long nativeHandle; // beatmap handle for internal usage 12 | 13 | /** 14 | * Constructor for class Beatmap. 15 | * Constructs a new beatmap with the given context. 16 | * @param ctx the context to associate the beatmap with. 17 | */ 18 | public Beatmap(OppaiCtx ctx) { 19 | newBeatmap(ctx); 20 | } 21 | 22 | /** 23 | * Parses a .osu file into a beatmap object. 24 | * @param path the path to the .osu file to parse or "-" to read from stdin 25 | * @param buffer the buffer object to use for containing the beatmap data. 26 | * @param disableCache passing true will disable caching. Passing false will use caching. 27 | * If false is given, cachePath should also be a valid path. 28 | * @param cachePath the path to cache beatmaps at. If no valid path is given, it will use the path of the current exectuable. 29 | */ 30 | public void parse(String path, Buffer buffer, boolean disableCache, String cachePath) { 31 | nativeParse(path, buffer, buffer.getBufferLength(), disableCache, cachePath); 32 | } 33 | 34 | /** 35 | * 36 | * @return the CS of the beatmap. 37 | */ 38 | public native float getCS(); 39 | /** 40 | * 41 | * @return the OD of the beatmap. 42 | */ 43 | public native float getOD(); 44 | /** 45 | * 46 | * @return the AR of the beatmap. 47 | */ 48 | public native float getAR(); 49 | /** 50 | * 51 | * @return the HP of the beatmap. 52 | */ 53 | public native float getHP(); 54 | 55 | /** 56 | * Sets the CS of the beatmap to the given CS. 57 | * @param cs the CS to set the beatmap's CS to. 58 | */ 59 | public native void setCS(float cs); 60 | /** 61 | * Sets the OD of the beatmap to the given OD. 62 | * @param od the OD to set the beatmap's OD to. 63 | */ 64 | public native void setOD(float od); 65 | /** 66 | * Sets the AR of the beatmap to the given AR. 67 | * @param ar the AR to set the beatmap's AR to. 68 | */ 69 | public native void setAR(float ar); 70 | 71 | /** 72 | * 73 | * @return the artist of the beatmap. 74 | */ 75 | public native String getArtist(); 76 | /** 77 | * 78 | * @return the title of the beatmap. 79 | */ 80 | public native String getTitle(); 81 | /** 82 | * 83 | * @return the version of the beatmap. 84 | */ 85 | public native String getVersion(); 86 | /** 87 | * 88 | * @return the creator of the beatmap. 89 | */ 90 | public native String getCreator(); 91 | 92 | /** 93 | * 94 | * @return the number of objects in the beatmap. 95 | */ 96 | public native int getNumObjects(); 97 | /** 98 | * 99 | * @return the number of circles in the beatmap. 100 | */ 101 | public native int getNumCircles(); 102 | /** 103 | * 104 | * @return the number of sliders in the beatmap. 105 | */ 106 | public native int getNumSliders(); 107 | /** 108 | * 109 | * @return the number of spinners in the beatmap. 110 | */ 111 | public native int getNumSpinners(); 112 | /** 113 | * 114 | * @return the max combo of the beatmap. 115 | */ 116 | public native int getMaxCombo(); 117 | 118 | /** 119 | * 120 | * @return the mode code of the beatmap. 121 | */ 122 | public native short getMode(); 123 | 124 | /** 125 | * Applies map-changing mods to a beatmap. 126 | * 127 | * @param modMask any combination of the mod constants bit-wise OR-ed together. (e.g Mods.HD | Mods.HR) 128 | */ 129 | public native void applyMods(int modMask); 130 | 131 | /** 132 | * Disposes of this beatmap instance. 133 | * Use this when this beatmap instance is no longer needed. 134 | * Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) 135 | * so this method is needed. It's very recommended to call this if you don't want memory leaks. 136 | * the Beatmap instance itself will still exist in the scope it was made, only the native object will no longer exist, so make sure you don't pass this instance 137 | * to any function anymore because the underlying native object will not exist and this will crash your program. 138 | */ 139 | public native void dispose(); 140 | 141 | private native void newBeatmap(OppaiCtx ctx); // used to initialize the beatmap handle 142 | private native void nativeParse(String path, Buffer buffer, int bufferLength, boolean disableCache, String cachePath); 143 | 144 | /** 145 | * Creates a string containing some of the metadata of the beatmap in a formatted way like so: 146 | * False Noise - Skyshards[Shroud](by Shiirn) 147 | * CS4.0 OD9.0 AR9.4 HP7.4 148 | * 1168 objects (601 circles, 565 sliders, 2 spinners) 149 | * max combo: 1978 150 | * @return a formatted string of the metadata of the beatmap. 151 | */ 152 | public String toString() { 153 | String beatmapInfo = ""; 154 | beatmapInfo += getArtist() + " - " + getTitle() + "[" + getVersion() + "]" + "(by " + getCreator() + ")\n"; 155 | 156 | beatmapInfo += "CS" + getCS() + " OD" + getOD() + " AR" + getAR() + " HP" +getHP() + "\n"; 157 | beatmapInfo += getNumObjects() + " objects (" + getNumCircles() + " circles, " + getNumSliders() + " sliders, " + getNumSpinners() + " spinners)\n"; 158 | beatmapInfo += "max combo: " + getMaxCombo(); 159 | 160 | return beatmapInfo; 161 | } 162 | 163 | static { 164 | System.loadLibrary("oppai"); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/Buffer.java: -------------------------------------------------------------------------------- 1 | package dp.oppai; 2 | /** 3 | * Buffer represents a buffer used for containing all the beatmap data. 4 | * 5 | * @author dpisdaniel 6 | * @version 1.0 7 | */ 8 | public class Buffer { 9 | private int bufferLength; 10 | private long nativeHandle; // This would be the pointer to the buffer 11 | 12 | /** 13 | * Constructor for the Buffer class. 14 | * Constructs a new buffer to hold the beatmap contents. 15 | * 16 | * @param bufferLength the size in bytes of the buffer. 17 | */ 18 | public Buffer(int bufferLength) { 19 | this.bufferLength = bufferLength; 20 | newBuffer(bufferLength); 21 | } 22 | 23 | /** 24 | * Retrieves the buffer's length. 25 | * 26 | * @return the size (length) of the buffer in bytes. 27 | */ 28 | public int getBufferLength() { 29 | return this.bufferLength; 30 | } 31 | 32 | /** 33 | * Disposes of this buffer instance. 34 | * Use this when this buffer instance is no longer needed. 35 | * Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) 36 | * so this method is needed. It's very recommended to call this if you don't want memory leaks. 37 | * the Buffer instance itself will still exist in the scope it was made, only the native object will no longer exist, so make sure you don't pass this instance 38 | * to any function anymore because the underlying native object will not exist and this will crash your program. 39 | */ 40 | public native void dispose(); 41 | 42 | private native void newBuffer(int bufferLength); 43 | 44 | static { 45 | System.loadLibrary("oppai"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/DiffCalc.java: -------------------------------------------------------------------------------- 1 | package dp.oppai; 2 | 3 | /** 4 | * The DiffCalc class is used for all calculations related to the difficulty of a beatmap. 5 | * 6 | * @author dpisdaniel 7 | * 8 | */ 9 | public class DiffCalc { 10 | 11 | public static final int DEFAULT_SINGLETAP_THRESHOLD = 125; 12 | 13 | private double stars; 14 | private double aim; 15 | private double speed; 16 | private double rhythmAwkwardness; 17 | private double nSingles; 18 | private double nSinglesTiming; 19 | private double nSinglesThreshold; 20 | 21 | /** 22 | * Calculates difficulty (star rating) for a beatmap. 23 | * @param b the Beatmap to calculate the difficulty of. 24 | * @param diffCtx oppai context object. 25 | * @param withAwkwardness if True, rhythm awkwardness will be calculated, otherwise, the {@link #rhythmAwkwardness} property will not be set. 26 | * @param withAimSingles if True, the number of aim singletaps will be calculated. Aim singletaps are seen by the difficulty calculator (based on 27 | * a spacing threshold). if False, the {@link #nSingles} property will not be set. 28 | * @param withTimingSingles if True, the number of 1/2 notes will be calculated. Otherwise, the {@link #nSinglesTiming} property will not be set. 29 | * @param withThresholdSingles if True, the number of notes that are 1/2 or slower at singletapThreshold ms will be calculated. Otherwise, 30 | * the {@link #nSinglesThreshold} property will not be set. 31 | * @param singletapThreshold The singletap threshold in ms. Use the default one if you don't care or know what this is. Alternatively, 32 | * @see Beatmap#diffCalc(OppaiCtx, boolean, boolean, boolean, boolean) 33 | */ 34 | public void diffCalc(Beatmap b, OppaiCtx diffCtx, boolean withAwkwardness, boolean withAimSingles, boolean withTimingSingles, boolean withThresholdSingles, int singletapThreshold ) { 35 | nativeDiffCalc(b, diffCtx, withAwkwardness, withAimSingles, withTimingSingles, withThresholdSingles, singletapThreshold); 36 | } 37 | 38 | /** 39 | * Calculates difficulty (star rating) for a beatmap. 40 | * @param b the Beatmap to calculate the difficulty of. 41 | * @param diffCtx oppai context object. 42 | * @param withAwkwardness if True, rhythm awkwardness will be calculated, otherwise, the {@link #rhythmAwkwardness} property will not be set. 43 | * @param withAimSingles if True, the number of aim singletaps will be calculated. Aim singletaps are seen by the difficulty calculator (based on 44 | * a spacing threshold). if False, the {@link #nSingles} property will not be set. 45 | * @param withTimingSingles if True, the number of 1/2 notes will be calculated. Otherwise, the {@link #nSinglesTiming} property will not be set. 46 | * @param withThresholdSingles if True, the number of notes that are 1/2 or slower at singletapThreshold ms will be calculated. Otherwise, 47 | * the {@link #nSinglesThreshold} property will not be set. 48 | */ 49 | public void diffCalc(Beatmap b, OppaiCtx diffCtx, boolean withAwkwardness, boolean withAimSingles, boolean withTimingSingles, boolean withThresholdSingles) { 50 | nativeDiffCalc(b, diffCtx, withAwkwardness, withAimSingles, withTimingSingles, withThresholdSingles, DEFAULT_SINGLETAP_THRESHOLD); 51 | } 52 | 53 | private native void nativeDiffCalc(Beatmap b, OppaiCtx diffCtx, boolean withAwkwardness, boolean withAimSingles, boolean withTimingSingles, boolean withThresholdSingles, int singletapThreshold); 54 | 55 | /** 56 | * 57 | * @return the star rating of the beatmap. 58 | */ 59 | public double getStars() { 60 | return stars; 61 | } 62 | 63 | /** 64 | * 65 | * @return the aim rating of the beatmap. 66 | */ 67 | public double getAim() { 68 | return aim; 69 | } 70 | 71 | /** 72 | * 73 | * @return the speed rating of the beatmap. 74 | */ 75 | public double getSpeed() { 76 | return speed; 77 | } 78 | 79 | /** 80 | * 81 | * @return the rhythm awkwardness of the beatmap. 82 | */ 83 | public double getRhythmAwkwardness() { 84 | return rhythmAwkwardness; 85 | } 86 | 87 | /** 88 | * 89 | * @return the number of singles of the beatmap. 90 | */ 91 | public double getnSingles() { 92 | return nSingles; 93 | } 94 | 95 | /** 96 | * 97 | * @return the singles timing of the beatmap. 98 | */ 99 | public double getnSinglesTiming() { 100 | return nSinglesTiming; 101 | } 102 | 103 | /** 104 | * 105 | * @return the singles threshold of the beatmap. 106 | */ 107 | public double getnSinglesThreshold() { 108 | return nSinglesThreshold; 109 | } 110 | 111 | static { 112 | System.loadLibrary("oppai"); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/Mods.java: -------------------------------------------------------------------------------- 1 | package dp.oppai; 2 | /** 3 | * Mods contains all the different mods constants for oppai's mods bitmask. 4 | * 5 | * @author dpisdaniel 6 | * @version 1.0 7 | */ 8 | public final class Mods { 9 | 10 | public static final int NOMOD = 0; 11 | public static final int NF = 1 << 0; 12 | public static final int EZ = 1 << 1; 13 | public static final int HD = 1 << 3; 14 | public static final int HR = 1 << 4; 15 | public static final int DT = 1 << 6; 16 | public static final int HT = 1 << 8; 17 | public static final int NC = 1 << 9; 18 | public static final int FL = 1 << 10; 19 | public static final int SO = 1 << 12; 20 | 21 | private Mods() { 22 | //this prevents even the native class from 23 | //calling this ctor as well 24 | throw new AssertionError(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/OppaiCtx.java: -------------------------------------------------------------------------------- 1 | package dp.oppai; 2 | /** 3 | * OppaiCtx represents an ctx struct. It can represent either a regular context or a diff context. 4 | * 5 | * @author dpisdaniel 6 | * @version 1.0 7 | */ 8 | public class OppaiCtx { 9 | private long nativeHandle; 10 | 11 | /** 12 | * Constructor for class OppaiCtx. 13 | * Constructs a new context to use with a beatmap. 14 | */ 15 | public OppaiCtx() { // This will create a regular ctx 16 | newCtx(); 17 | } 18 | 19 | /** 20 | * Constructor for class OppaiCtx. 21 | * Constructs a new difficulty context to use when calculating beatmap difficulty 22 | * 23 | * @param ctx - The context that is associated with 24 | */ 25 | public OppaiCtx(OppaiCtx ctx) { // This will create a diff ctx 26 | newDiffCalcContext(ctx); 27 | } 28 | 29 | /** 30 | * Retrieves the handle for this context. 31 | * @return the handle for this context. 32 | */ 33 | public long getContextHandle() { 34 | return nativeHandle; 35 | } 36 | 37 | /** 38 | * Retrieves the last error from this context. 39 | * If no error is found, returns an empty string. 40 | * @return The last error's string. 41 | */ 42 | public String getLastErr() { 43 | return nativeGetLastErr(); 44 | } 45 | 46 | private native void newCtx(); 47 | 48 | private native void newDiffCalcContext(OppaiCtx ctx); 49 | 50 | private native String nativeGetLastErr(); 51 | 52 | /** 53 | * Disposes of this OppaiCtx instance. 54 | * Use this when this OppaiCtx instance is no longer needed. 55 | * Sadly it's hard to get rid of our native objects with Java's garbage collector and no ideal alternative is given in java (like C++'s destructors) 56 | * so this method is needed. It's very recommended to call this if you don't want memory leaks. 57 | * the OppaiCtx instance itself will still exist in the scope it was made, only the native object will no longer exist, so make sure you don't pass this instance 58 | * to any function anymore because the underlying native object will not exist and this will crash your program. 59 | */ 60 | public native void dispose(); 61 | 62 | static { 63 | System.loadLibrary("oppai"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/PPCalc.java: -------------------------------------------------------------------------------- 1 | package dp.oppai; 2 | 3 | /** 4 | * The PPCalc class is used for all calculations related to pp calculations of a beatmap. 5 | * 6 | * @author dpisdaniel 7 | * 8 | */ 9 | public class PPCalc { 10 | 11 | public static final short DEFAULT_COMBO = (short)0xFFFF; 12 | public static final short DEFAULT_MISSES = 0; 13 | public static final short DEFAULT_C300 = (short)0xFFFF; 14 | public static final short DEFAULT_C100 = 0; 15 | public static final short DEFAULT_C50 = 0; 16 | public static final int DEFAULT_SCORE_VER = 1; 17 | public static final double DEFAULT_ACCURACY = 100.0; 18 | 19 | private double accPercent; 20 | private double pp; 21 | private double aimPP; 22 | private double speedPP; 23 | private double accPP; 24 | 25 | /** 26 | * 27 | * @return the acc percent of the beatmap. 28 | */ 29 | public double getAccPercent() { 30 | return accPercent; 31 | } 32 | 33 | /** 34 | * 35 | * @return the pp of the beatmap given by the acc percent and applied mods. 36 | */ 37 | public double getPp() { 38 | return pp; 39 | } 40 | 41 | /** 42 | * 43 | * @return the aim pp of the beatmap given by the acc percent and applied mods. 44 | */ 45 | public double getAimPP() { 46 | return aimPP; 47 | } 48 | 49 | /** 50 | * 51 | * @return the speed pp of the beatmap given by the acc percent and applied mods. 52 | */ 53 | public double getSpeedPP() { 54 | return speedPP; 55 | } 56 | 57 | /** 58 | * 59 | * @return the acc pp of the beatmap given by the acc percent and applied mods. 60 | */ 61 | public double getAccPP() { 62 | return accPP; 63 | } 64 | 65 | // A clusterfuck due to java not allowing optional parameters smh 66 | // TODO: Try to make this work with varargs 67 | 68 | /** 69 | * Calculates pp for a beatmap. 70 | * Sets the results in {@link #accPercent}, {@link #pp}, {@link #aimPP}, {@link #speedPP} and {@link #accPP} 71 | * Use other overloads of this function if you don't want to pass some of these values. 72 | * @param b the Beatmap to calc the pp for 73 | * @param ctx the oppai context object 74 | * @param aim aim stars. {@link DiffCalc#aim} 75 | * @param speed speed stars. {@link DiffCalc#stars} 76 | * @param usedMods any combination of the mod constants bit-wise OR-ed together (example: Mods.HD | Mods.HR) defaults to {@link Mods#NOMOD} 77 | * @param combo the combo. defaults to {@link #DEFAULT_COMBO} 78 | * @param misses the num. of misses. defaults to {@link #DEFAULT_MISSES} 79 | * @param c300 the num. of c300. defaults to {@link #DEFAULT_C300} 80 | * @param c100 the num. of c100. defaults to {@link #DEFAULT_C100} 81 | * @param c50 the num. of c50. defaults to {@link #DEFAULT_C50} 82 | * @param scoreVersion 1 or 2. the default is 1. scorev2 affects accuracy pp 83 | */ 84 | public void calcPP(Beatmap b, OppaiCtx ctx, double aim, double speed, int usedMods, int combo, int misses, int c300, int c100, int c50, int scoreVersion) { 85 | nativeCalcPP(b, ctx, aim, speed, usedMods, combo, misses, c300, c100, c50, scoreVersion); 86 | } 87 | 88 | /** 89 | * Overloaded function. 90 | * For the default values see {link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} 91 | */ 92 | public void calcPP(Beatmap b, OppaiCtx ctx, double aim, double speed, int usedMods) { 93 | nativeCalcPP(b, ctx, aim, speed, usedMods, DEFAULT_COMBO, DEFAULT_MISSES, DEFAULT_C300, DEFAULT_C100, DEFAULT_C50, DEFAULT_SCORE_VER); 94 | } 95 | 96 | /** 97 | * Overloaded function. 98 | * For the default values see {link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} 99 | */ 100 | public void calcPP(Beatmap b, OppaiCtx ctx, int aim, int speed, int usedMods, int combo, int misses, int c300, int c100, int c50, int scoreVersion) { 101 | nativeCalcPP(b, ctx, aim, speed, usedMods, combo, misses, c300, c100, c50, scoreVersion); 102 | } 103 | 104 | /** 105 | * Overloaded function. 106 | * For the default values see {link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} 107 | */ 108 | public void calcPP(Beatmap b, OppaiCtx ctx, double aim, double speed) { 109 | nativeCalcPP(b, ctx, aim, speed, Mods.NOMOD, DEFAULT_COMBO, DEFAULT_MISSES, DEFAULT_C300, DEFAULT_C100, DEFAULT_C50, DEFAULT_SCORE_VER); 110 | } 111 | 112 | /** 113 | * Overloaded function. 114 | * For more options and an explanation see {@link #calcPPAcc(Beatmap, OppaiCtx, double, double, double, int, int, int, int)} 115 | * @param b the Beatmap to calc the pp for 116 | * @param ctx oppai ctx object see {@link OppaiCtx#OppaiCtx()} 117 | * @param aim aim stars see {@link DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean)} 118 | * @param speed speed stars see {@link DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean)} 119 | * @param acc the accuracy in percentage to calculate 120 | */ 121 | public void calcPPAcc(Beatmap b, OppaiCtx ctx, double aim, double speed, double acc) { 122 | nativeCalcPPAcc(b, ctx, aim, speed, acc, Mods.NOMOD, DEFAULT_COMBO, DEFAULT_MISSES, DEFAULT_SCORE_VER); 123 | } 124 | 125 | /** 126 | * Sets the results in {@link #accPercent}, {@link #pp}, {@link #aimPP}, {@link #speedPP} and {@link #accPP} 127 | * Same as {@link #calcPP(Beatmap, OppaiCtx, double, double, int, int, int, int, int, int, int)} but with percentage accuracy. 128 | * 129 | * @param b the Beatmap to calc the pp for 130 | * @param ctx oppai ctx object see {@link OppaiCtx#OppaiCtx()} 131 | * @param aim aim stars see {@link DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean)} 132 | * @param speed speed speed stars see {@link DiffCalc#diffCalc(Beatmap, OppaiCtx, boolean, boolean, boolean, boolean)} 133 | * @param acc acc the accuracy in percentage to calculate 134 | * @param usedMods any combination of the mod constants bit-wise OR-ed together (e.g Mods.HD | Mods.HR) defaults to {@link Mods#NOMOD} 135 | * @param combo the combo. defaults to {@link #DEFAULT_COMBO} 136 | * @param misses the num. of misses. defaults to {@link #DEFAULT_MISSES} 137 | * @param scoreVersion 1 or 2. the default is 1. scorev2 affects accuracy pp 138 | */ 139 | public void calcPPAcc(Beatmap b, OppaiCtx ctx, double aim, double speed, double acc, int usedMods, int combo, int misses, int scoreVersion) { 140 | nativeCalcPPAcc(b, ctx, aim, speed, acc, usedMods, combo, misses, scoreVersion); 141 | } 142 | 143 | private native void nativeCalcPP(Beatmap b, OppaiCtx ctx, double aim, double speed, int usedMods, int combo, int misses, int c300, int c100, int c50, int scoreVersion); 144 | private native void nativeCalcPPAcc(Beatmap b, OppaiCtx ctx, double aim, double speed, double acc, int usedMods, int combo, int misses, int scoreVersion); 145 | 146 | static { 147 | System.loadLibrary("oppai"); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /java-oppai/src/dp/oppai/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dpisdaniel 3 | * 4 | */ 5 | package dp.oppai; -------------------------------------------------------------------------------- /java-oppai/src/dp_oppai_Beatmap.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class dp_oppai_Beatmap */ 4 | 5 | #ifndef _Included_dp_oppai_Beatmap 6 | #define _Included_dp_oppai_Beatmap 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: dp_oppai_Beatmap 12 | * Method: getCS 13 | * Signature: ()F 14 | */ 15 | JNIEXPORT jfloat JNICALL Java_dp_oppai_Beatmap_getCS 16 | (JNIEnv *, jobject); 17 | 18 | /* 19 | * Class: dp_oppai_Beatmap 20 | * Method: getOD 21 | * Signature: ()F 22 | */ 23 | JNIEXPORT jfloat JNICALL Java_dp_oppai_Beatmap_getOD 24 | (JNIEnv *, jobject); 25 | 26 | /* 27 | * Class: dp_oppai_Beatmap 28 | * Method: getAR 29 | * Signature: ()F 30 | */ 31 | JNIEXPORT jfloat JNICALL Java_dp_oppai_Beatmap_getAR 32 | (JNIEnv *, jobject); 33 | 34 | /* 35 | * Class: dp_oppai_Beatmap 36 | * Method: getHP 37 | * Signature: ()F 38 | */ 39 | JNIEXPORT jfloat JNICALL Java_dp_oppai_Beatmap_getHP 40 | (JNIEnv *, jobject); 41 | 42 | /* 43 | * Class: dp_oppai_Beatmap 44 | * Method: setCS 45 | * Signature: (F)V 46 | */ 47 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_setCS 48 | (JNIEnv *, jobject, jfloat); 49 | 50 | /* 51 | * Class: dp_oppai_Beatmap 52 | * Method: setOD 53 | * Signature: (F)V 54 | */ 55 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_setOD 56 | (JNIEnv *, jobject, jfloat); 57 | 58 | /* 59 | * Class: dp_oppai_Beatmap 60 | * Method: setAR 61 | * Signature: (F)V 62 | */ 63 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_setAR 64 | (JNIEnv *, jobject, jfloat); 65 | 66 | /* 67 | * Class: dp_oppai_Beatmap 68 | * Method: getArtist 69 | * Signature: ()Ljava/lang/String; 70 | */ 71 | JNIEXPORT jstring JNICALL Java_dp_oppai_Beatmap_getArtist 72 | (JNIEnv *, jobject); 73 | 74 | /* 75 | * Class: dp_oppai_Beatmap 76 | * Method: getTitle 77 | * Signature: ()Ljava/lang/String; 78 | */ 79 | JNIEXPORT jstring JNICALL Java_dp_oppai_Beatmap_getTitle 80 | (JNIEnv *, jobject); 81 | 82 | /* 83 | * Class: dp_oppai_Beatmap 84 | * Method: getVersion 85 | * Signature: ()Ljava/lang/String; 86 | */ 87 | JNIEXPORT jstring JNICALL Java_dp_oppai_Beatmap_getVersion 88 | (JNIEnv *, jobject); 89 | 90 | /* 91 | * Class: dp_oppai_Beatmap 92 | * Method: getCreator 93 | * Signature: ()Ljava/lang/String; 94 | */ 95 | JNIEXPORT jstring JNICALL Java_dp_oppai_Beatmap_getCreator 96 | (JNIEnv *, jobject); 97 | 98 | /* 99 | * Class: dp_oppai_Beatmap 100 | * Method: getNumObjects 101 | * Signature: ()I 102 | */ 103 | JNIEXPORT jint JNICALL Java_dp_oppai_Beatmap_getNumObjects 104 | (JNIEnv *, jobject); 105 | 106 | /* 107 | * Class: dp_oppai_Beatmap 108 | * Method: getNumCircles 109 | * Signature: ()I 110 | */ 111 | JNIEXPORT jint JNICALL Java_dp_oppai_Beatmap_getNumCircles 112 | (JNIEnv *, jobject); 113 | 114 | /* 115 | * Class: dp_oppai_Beatmap 116 | * Method: getNumSliders 117 | * Signature: ()I 118 | */ 119 | JNIEXPORT jint JNICALL Java_dp_oppai_Beatmap_getNumSliders 120 | (JNIEnv *, jobject); 121 | 122 | /* 123 | * Class: dp_oppai_Beatmap 124 | * Method: getNumSpinners 125 | * Signature: ()I 126 | */ 127 | JNIEXPORT jint JNICALL Java_dp_oppai_Beatmap_getNumSpinners 128 | (JNIEnv *, jobject); 129 | 130 | /* 131 | * Class: dp_oppai_Beatmap 132 | * Method: getMaxCombo 133 | * Signature: ()I 134 | */ 135 | JNIEXPORT jint JNICALL Java_dp_oppai_Beatmap_getMaxCombo 136 | (JNIEnv *, jobject); 137 | 138 | /* 139 | * Class: dp_oppai_Beatmap 140 | * Method: getMode 141 | * Signature: ()S 142 | */ 143 | JNIEXPORT jshort JNICALL Java_dp_oppai_Beatmap_getMode 144 | (JNIEnv *, jobject); 145 | 146 | /* 147 | * Class: dp_oppai_Beatmap 148 | * Method: applyMods 149 | * Signature: (I)V 150 | */ 151 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_applyMods 152 | (JNIEnv *, jobject, jint); 153 | 154 | /* 155 | * Class: dp_oppai_Beatmap 156 | * Method: dispose 157 | * Signature: ()V 158 | */ 159 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_dispose 160 | (JNIEnv *, jobject); 161 | 162 | /* 163 | * Class: dp_oppai_Beatmap 164 | * Method: newBeatmap 165 | * Signature: (Ldp/oppai/OppaiCtx;)V 166 | */ 167 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_newBeatmap 168 | (JNIEnv *, jobject, jobject); 169 | 170 | /* 171 | * Class: dp_oppai_Beatmap 172 | * Method: nativeParse 173 | * Signature: (Ljava/lang/String;Ldp/oppai/Buffer;IZLjava/lang/String;)V 174 | */ 175 | JNIEXPORT void JNICALL Java_dp_oppai_Beatmap_nativeParse 176 | (JNIEnv *, jobject, jstring, jobject, jint, jboolean, jstring); 177 | 178 | #ifdef __cplusplus 179 | } 180 | #endif 181 | #endif 182 | -------------------------------------------------------------------------------- /java-oppai/src/dp_oppai_Buffer.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class dp_oppai_Buffer */ 4 | 5 | #ifndef _Included_dp_oppai_Buffer 6 | #define _Included_dp_oppai_Buffer 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: dp_oppai_Buffer 12 | * Method: dispose 13 | * Signature: ()V 14 | */ 15 | JNIEXPORT void JNICALL Java_dp_oppai_Buffer_dispose 16 | (JNIEnv *, jobject); 17 | 18 | /* 19 | * Class: dp_oppai_Buffer 20 | * Method: newBuffer 21 | * Signature: (I)V 22 | */ 23 | JNIEXPORT void JNICALL Java_dp_oppai_Buffer_newBuffer 24 | (JNIEnv *, jobject, jint); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | #endif 30 | -------------------------------------------------------------------------------- /java-oppai/src/dp_oppai_DiffCalc.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class dp_oppai_DiffCalc */ 4 | 5 | #ifndef _Included_dp_oppai_DiffCalc 6 | #define _Included_dp_oppai_DiffCalc 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | #undef dp_oppai_DiffCalc_DEFAULT_SINGLETAP_THRESHOLD 11 | #define dp_oppai_DiffCalc_DEFAULT_SINGLETAP_THRESHOLD 125L 12 | /* 13 | * Class: dp_oppai_DiffCalc 14 | * Method: nativeDiffCalc 15 | * Signature: (Ldp/oppai/Beatmap;Ldp/oppai/OppaiCtx;ZZZZI)V 16 | */ 17 | JNIEXPORT void JNICALL Java_dp_oppai_DiffCalc_nativeDiffCalc 18 | (JNIEnv *, jobject, jobject, jobject, jboolean, jboolean, jboolean, jboolean, jint); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | #endif 24 | -------------------------------------------------------------------------------- /java-oppai/src/dp_oppai_OppaiCtx.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class dp_oppai_OppaiCtx */ 4 | 5 | #ifndef _Included_dp_oppai_OppaiCtx 6 | #define _Included_dp_oppai_OppaiCtx 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: dp_oppai_OppaiCtx 12 | * Method: newCtx 13 | * Signature: ()V 14 | */ 15 | JNIEXPORT void JNICALL Java_dp_oppai_OppaiCtx_newCtx 16 | (JNIEnv *, jobject); 17 | 18 | /* 19 | * Class: dp_oppai_OppaiCtx 20 | * Method: newDiffCalcContext 21 | * Signature: (Ldp/oppai/OppaiCtx;)V 22 | */ 23 | JNIEXPORT void JNICALL Java_dp_oppai_OppaiCtx_newDiffCalcContext 24 | (JNIEnv *, jobject, jobject); 25 | 26 | /* 27 | * Class: dp_oppai_OppaiCtx 28 | * Method: nativeGetLastErr 29 | * Signature: ()Ljava/lang/String; 30 | */ 31 | JNIEXPORT jstring JNICALL Java_dp_oppai_OppaiCtx_nativeGetLastErr 32 | (JNIEnv *, jobject); 33 | 34 | /* 35 | * Class: dp_oppai_OppaiCtx 36 | * Method: dispose 37 | * Signature: ()V 38 | */ 39 | JNIEXPORT void JNICALL Java_dp_oppai_OppaiCtx_dispose 40 | (JNIEnv *, jobject); 41 | 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | #endif 46 | -------------------------------------------------------------------------------- /java-oppai/src/dp_oppai_PPCalc.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class dp_oppai_PPCalc */ 4 | 5 | #ifndef _Included_dp_oppai_PPCalc 6 | #define _Included_dp_oppai_PPCalc 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | #undef dp_oppai_PPCalc_DEFAULT_COMBO 11 | #define dp_oppai_PPCalc_DEFAULT_COMBO -1L 12 | #undef dp_oppai_PPCalc_DEFAULT_MISSES 13 | #define dp_oppai_PPCalc_DEFAULT_MISSES 0L 14 | #undef dp_oppai_PPCalc_DEFAULT_C300 15 | #define dp_oppai_PPCalc_DEFAULT_C300 -1L 16 | #undef dp_oppai_PPCalc_DEFAULT_C100 17 | #define dp_oppai_PPCalc_DEFAULT_C100 0L 18 | #undef dp_oppai_PPCalc_DEFAULT_C50 19 | #define dp_oppai_PPCalc_DEFAULT_C50 0L 20 | #undef dp_oppai_PPCalc_DEFAULT_SCORE_VER 21 | #define dp_oppai_PPCalc_DEFAULT_SCORE_VER 1L 22 | #undef dp_oppai_PPCalc_DEFAULT_ACCURACY 23 | #define dp_oppai_PPCalc_DEFAULT_ACCURACY 100.0 24 | /* 25 | * Class: dp_oppai_PPCalc 26 | * Method: nativeCalcPP 27 | * Signature: (Ldp/oppai/Beatmap;Ldp/oppai/OppaiCtx;DDIIIIIII)V 28 | */ 29 | JNIEXPORT void JNICALL Java_dp_oppai_PPCalc_nativeCalcPP 30 | (JNIEnv *, jobject, jobject, jobject, jdouble, jdouble, jint, jint, jint, jint, jint, jint, jint); 31 | 32 | /* 33 | * Class: dp_oppai_PPCalc 34 | * Method: nativeCalcPPAcc 35 | * Signature: (Ldp/oppai/Beatmap;Ldp/oppai/OppaiCtx;DDDIIII)V 36 | */ 37 | JNIEXPORT void JNICALL Java_dp_oppai_PPCalc_nativeCalcPPAcc 38 | (JNIEnv *, jobject, jobject, jobject, jdouble, jdouble, jdouble, jint, jint, jint, jint); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | #endif 44 | -------------------------------------------------------------------------------- /java-oppai/src/handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | jfieldID getHandleField(JNIEnv *env, jobject obj) { 4 | jclass c = env->GetObjectClass(obj); 5 | // J is the type signature for long: 6 | return env->GetFieldID(c, "nativeHandle", "J"); 7 | } 8 | 9 | template 10 | T* getHandle(JNIEnv *env, jobject obj) { 11 | jlong handle = env->GetLongField(obj, getHandleField(env, obj)); 12 | return reinterpret_cast(handle); 13 | } 14 | 15 | template 16 | void setHandle(JNIEnv *env, jobject obj, T *t) { 17 | jlong handle = reinterpret_cast(t); 18 | env->SetLongField(obj, getHandleField(env, obj), handle); 19 | } 20 | -------------------------------------------------------------------------------- /lib_example/amd64-msvc2010.bat: -------------------------------------------------------------------------------- 1 | call C:\\"Program Files"\\"Microsoft Visual Studio 10.0"\\VC\\vcvarsall.bat x86_amd64 2 | call build.bat 3 | -------------------------------------------------------------------------------- /lib_example/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | del test.exe 4 | del test.obj 5 | cl -D_CRT_SECURE_NO_WARNINGS=1 ^ 6 | -DNOMINMAX=1 ^ 7 | -DOPPAI_LIB=1 ^ 8 | -O2 ^ 9 | -nologo -MT -Gm- -GR- -EHsc -W4 -WX ^ 10 | -wd4201 ^ 11 | -wd4100 ^ 12 | -F8000000 ^ 13 | main.cc ^ 14 | -Fetest.exe ^ 15 | Advapi32.lib 16 | -------------------------------------------------------------------------------- /lib_example/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ${CXX:-g++} \ 4 | -std=c++98 \ 5 | -pedantic \ 6 | -O2 \ 7 | $@ \ 8 | -Wno-variadic-macros \ 9 | -Wall -Werror \ 10 | main.cc \ 11 | -lm -lstdc++ \ 12 | -lcrypto \ 13 | -DOPPAI_LIB=1 \ 14 | -o test \ 15 | \ 16 | && strip -R .comment ./test 17 | -------------------------------------------------------------------------------- /lib_example/i386-msvc2010.bat: -------------------------------------------------------------------------------- 1 | call C:\\"Program Files"\\"Microsoft Visual Studio 10.0"\\VC\\vcvarsall.bat x86 2 | call build.bat 3 | -------------------------------------------------------------------------------- /lib_example/main.cc: -------------------------------------------------------------------------------- 1 | //#include "/path/to/oppai/code/main.cc" 2 | #include "../main.cc" 3 | 4 | // don't forget to define OPPAI_LIB=1 in your build script! 5 | 6 | void print_pp(pp_calc_result &res) 7 | { 8 | printf( 9 | "\n%.17g aim\n%.17g speed\n%.17g acc\n%.17g pp\nfor %.17g%%\n", 10 | res.aim_pp, res.speed_pp, res.acc_pp, res.pp, res.acc_percent 11 | ); 12 | } 13 | 14 | void print_diff(f64 stars, f64 aim, f64 speed) 15 | { 16 | printf( 17 | "\n%.17g stars\n%.17g aim stars\n%.17g speed stars\n", 18 | stars, aim, speed 19 | ); 20 | } 21 | 22 | // should be big enough to hold your osu file 23 | #define BUFSIZE 2000000 24 | static char buf[BUFSIZE]; 25 | 26 | void check_err(oppai_ctx* ctx) 27 | { 28 | if (oppai_err(ctx)) { 29 | fprintf(stderr, "%s\n", oppai_err(ctx)); 30 | exit(1); 31 | } 32 | } 33 | 34 | int main(int argc, char* argv[]) 35 | { 36 | if (argc != 2) { 37 | fprintf(stderr, "Usage: %s file.osu\n", argv[0]); 38 | return 1; 39 | } 40 | 41 | // if you need to multithread, create one ctx and buffer for each thread 42 | oppai_ctx ctx; 43 | 44 | // parse beatmap ----------------------------------------------------------- 45 | beatmap b(&ctx); 46 | beatmap::parse(argv[1], b, buf, BUFSIZE); 47 | // beatmaps will be cached in the executable's folder 48 | 49 | check_err(&ctx); 50 | 51 | printf( 52 | "%s - %s [%s] (by %s)\n" 53 | "CS%g OD%g AR%g HP%g\n" 54 | "%u objects (%u circles, %u sliders, %u spinners)\n" 55 | "max combo: %u\n", 56 | b.artist, b.title, b.version, b.creator, 57 | b.cs, b.od, b.ar, b.hp, 58 | (u32)b.num_objects, 59 | (u32)b.num_circles, 60 | (u32)b.num_sliders, 61 | (u32)b.num_spinners, 62 | (u32)b.max_combo 63 | ); 64 | 65 | // diff calc --------------------------------------------------------------- 66 | f64 stars, aim = 0, speed = 0; 67 | 68 | d_calc_ctx dctx(&ctx); 69 | stars = d_calc(&dctx, b, &aim, &speed); 70 | 71 | check_err(&ctx); 72 | print_diff(stars, aim, speed); 73 | 74 | // pp calc ----------------------------------------------------------------- 75 | pp_calc_result res = 76 | pp_calc(&ctx, aim, speed, b); 77 | 78 | check_err(&ctx); 79 | print_pp(res); 80 | 81 | // pp calc (with acc %) ---------------------------------------------------- 82 | res = pp_calc_acc(&ctx, aim, speed, b, 90.0); 83 | check_err(&ctx); 84 | 85 | print_pp(res); 86 | 87 | // override OD example ----------------------------------------------------- 88 | printf("\n----\nIf the map was od10:\n"); 89 | 90 | f32 old_od = b.od; 91 | b.od = 10; 92 | 93 | res = pp_calc(&ctx, aim, speed, b); 94 | check_err(&ctx); 95 | 96 | print_pp(res); 97 | 98 | b.od = old_od; 99 | 100 | // override AR example ----------------------------------------------------- 101 | printf("\n----\nIf the map was ar11:\n"); 102 | 103 | f32 old_ar = b.ar; 104 | b.ar = 11; 105 | 106 | res = pp_calc(&ctx, aim, speed, b); 107 | check_err(&ctx); 108 | 109 | print_pp(res); 110 | 111 | b.ar = old_ar; 112 | 113 | // override CS example ----------------------------------------------------- 114 | printf("\n----\nIf the map was cs6.5:\n"); 115 | 116 | f32 old_cs = b.cs; 117 | b.cs = 6.5; 118 | 119 | // remember that CS is map-changing so difficulty must be recomputed 120 | stars = d_calc(&dctx, b, &aim, &speed); 121 | check_err(&ctx); 122 | 123 | print_diff(stars, aim, speed); 124 | 125 | res = pp_calc(&ctx, aim, speed, b); 126 | check_err(&ctx); 127 | 128 | print_pp(res); 129 | 130 | b.cs = old_cs; 131 | 132 | // mods example ------------------------------------------------------------ 133 | printf("\n----\nWith HDHR:\n"); 134 | 135 | // mods are a bitmask, same as what the osu! api uses 136 | u32 mods = mods::hd | mods::hr; 137 | b.apply_mods(mods); 138 | 139 | // mods are map-changing, recompute diff 140 | stars = d_calc(&dctx, b, &aim, &speed); 141 | check_err(&ctx); 142 | 143 | print_diff(stars, aim, speed); 144 | 145 | res = pp_calc(&ctx, aim, speed, b, mods); 146 | check_err(&ctx); 147 | 148 | print_pp(res); 149 | 150 | return 0; 151 | } 152 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include "types.hh" 2 | #include "common.hh" 3 | 4 | // at the moment I'm not using static anywhere since it's a monolithic build 5 | // anyways. it might be nice to have it though, for extra scope safety 6 | 7 | // common includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include // DBL_MAX 14 | 15 | #include // tolower/toupper 16 | 17 | char toupper_wrapper(char c) { return (char)toupper(c); } 18 | char tolower_wrapper(char c) { return (char)tolower(c); } 19 | 20 | #ifndef OPPAI_LIB 21 | #define OPPAIAPI internalfn 22 | #define VERSION_SUFFIX 23 | #else 24 | // these are not thread safe so we must disable them in lib mode 25 | #undef OPPAI_PROFILER 26 | #undef SHOW_BEATMAP 27 | #undef _DEBUG 28 | 29 | #define OPPAIAPI 30 | #define VERSION_SUFFIX "-lib" 31 | #endif 32 | 33 | const char* version_string = "0.9.13" VERSION_SUFFIX; 34 | 35 | // ----------------------------------------------------------------------------- 36 | 37 | struct oppai_ctx 38 | { 39 | const char* last_err; 40 | 41 | oppai_ctx() : last_err(0) {} 42 | }; 43 | 44 | // returns the last error, or 0 if no error has occurred 45 | OPPAIAPI 46 | const char* oppai_err(oppai_ctx* ctx) { 47 | return ctx->last_err; 48 | } 49 | 50 | // ----------------------------------------------------------------------------- 51 | 52 | // sets the last error to msg if it's not already set 53 | #define die(ctx, msg) dbgputs(msg); die_impl(ctx, msg) 54 | 55 | internalfn 56 | void die_impl(oppai_ctx* ctx, const char* msg) 57 | { 58 | if (ctx->last_err) { 59 | return; 60 | } 61 | 62 | ctx->last_err = msg; 63 | } 64 | 65 | #ifdef OPPAI_LIB 66 | #define chk(ctx) \ 67 | if (oppai_err(ctx)) { \ 68 | return 1; \ 69 | } 70 | #else 71 | internalfn 72 | void chk(oppai_ctx* ctx) 73 | { 74 | if (!oppai_err(ctx)) { 75 | return; 76 | } 77 | 78 | fputs(oppai_err(ctx), stderr); 79 | fputs("\n", stderr); 80 | exit(1); 81 | } 82 | #endif 83 | 84 | // ----------------------------------------------------------------------------- 85 | 86 | // these don't necessarily need to match the pp processor's bitmask 1:1 but 87 | // I'll keep them consistent just because 88 | namespace mods 89 | { 90 | const u32 91 | nomod = 0, 92 | nf = 1 << 0, 93 | ez = 1 << 1, 94 | hd = 1 << 3, 95 | hr = 1 << 4, 96 | dt = 1 << 6, 97 | ht = 1 << 8, 98 | nc = 1 << 9, 99 | fl = 1 << 10, 100 | so = 1 << 12, 101 | speed_changing = dt | ht | nc, 102 | map_changing = hr | ez | speed_changing; 103 | } 104 | 105 | const char* const mod_strs[] = { 106 | "nomod", "nf", "ez", "hd", "hr", "dt", "ht", "nc", "fl", "so" 107 | }; 108 | 109 | const u32 mod_masks[] = { 110 | mods::nomod, mods::nf, mods::ez, mods::hd, mods::hr, mods::dt, mods::ht, 111 | mods::nc, mods::fl, mods::so 112 | }; 113 | 114 | const size_t num_mods = sizeof(mod_masks) / sizeof(mod_masks[0]); 115 | 116 | // ----------------------------------------------------------------------------- 117 | 118 | // TODO: move this? 119 | internalfn bool encode_str(FILE* fd, const char* str); 120 | 121 | // this a monolithic build. stuff is separated into files purely for readability 122 | #include "profiler.cc" 123 | #include "math.cc" 124 | #include "beatmap.cc" 125 | #include "diff_calc.cc" 126 | #include "pp_calc.cc" 127 | 128 | // ----------------------------------------------------------------------------- 129 | 130 | #ifdef SHOW_BEATMAP 131 | internalfn void print_beatmap(beatmap& b, oppai_ctx* ctx); 132 | #endif 133 | 134 | // ----------------------------------------------------------------------------- 135 | 136 | // !!!!!!!!! OVERRIDE PRINTF AND PUTS FOR OUTPUT MODULES !!!!!!!!!! 137 | #define printf(fmt, ...) fprintf(fd, fmt, __VA_ARGS__) 138 | #define puts(s) fputs(s "\n", fd) 139 | #define putchar(c) fputc(c, fd) 140 | 141 | // output modules 142 | #define print_sig(name) \ 143 | void name( \ 144 | FILE* fd, \ 145 | char* mods_str, \ 146 | u16 combo, \ 147 | u16 misses, \ 148 | u32 scoring, \ 149 | f64 stars, \ 150 | f64 aim, \ 151 | f64 speed, \ 152 | bool has_awkwardness, \ 153 | f64 rhythm_awkwardness, \ 154 | u16 nsingles, \ 155 | u16 nsingles_timing, \ 156 | u16 nsingles_threshold, \ 157 | pp_calc_result& res, \ 158 | beatmap& b) 159 | 160 | #define twodecimals(x) (macro_round((x) * 100.0) / 100.0) 161 | 162 | // text output 163 | internalfn 164 | print_sig(text_print) 165 | { 166 | // round to 2 decimal places 167 | aim = twodecimals(aim); 168 | speed = twodecimals(speed); 169 | stars = twodecimals(stars); 170 | f64 pp = twodecimals(res.pp); 171 | f64 aim_pp = twodecimals(res.aim_pp); 172 | f64 speed_pp = twodecimals(res.speed_pp); 173 | f64 acc_pp = twodecimals(res.acc_pp); 174 | f64 acc_percent = twodecimals(res.acc_percent); 175 | 176 | printf("o p p a i | v%s\n", version_string); 177 | puts("s d n | "); 178 | puts("u v s | (looking for"); 179 | puts("! a p | cool ascii"); 180 | puts(" n e | to put here)"); 181 | puts(" c c | "); 182 | puts(" e t | "); 183 | puts(" d o | "); 184 | puts(" r |\n"); 185 | 186 | printf("\n%s - %s [%s] (%s) %s\n", 187 | b.artist, b.title, b.version, b.creator, mods_str ? mods_str : ""); 188 | 189 | printf( 190 | "od%g ar%g cs%g hp%g\n", 191 | (i32)(b.od * 100.0) / 100.0, 192 | (i32)(b.ar * 100.0) / 100.0, 193 | (i32)(b.cs * 100.0) / 100.0, 194 | (i32)(b.hp * 100.0) / 100.0 195 | ); 196 | 197 | printf("%" fu16 "/%" fu16 " combo\n", combo, b.max_combo); 198 | printf("%" fu16 " circles, %" fu16 " sliders %" fu16 " spinners\n", 199 | b.num_circles, b.num_sliders, b.num_spinners); 200 | printf("%" fu16 "xmiss\n", misses); 201 | printf("%g%%\n", acc_percent); 202 | printf("scorev%" fu32"\n\n", scoring); 203 | 204 | printf("%g stars\naim stars: %g, speed stars: %g\n\n", stars, aim, speed); 205 | 206 | printf("aim: %g\n", aim_pp); 207 | printf("speed: %g\n", speed_pp); 208 | printf("accuracy: %g\n", acc_pp); 209 | 210 | if (has_awkwardness) 211 | { 212 | printf("\nrhythm awkwardness (beta): %g\n", rhythm_awkwardness); 213 | 214 | f64 awkwardness_bonus = 215 | std::max(1.0, std::min(1.15, std::pow(rhythm_awkwardness, 0.3))); 216 | printf("awkwardness acc pp bonus (beta): %g\n", awkwardness_bonus); 217 | } 218 | 219 | // first object is ignored since it has no previous to compare with 220 | u16 nmaxsingles = b.num_circles + b.num_sliders - 1; 221 | 222 | printf("%" fu16 " spacing singletaps (%g%%)\n", 223 | nsingles, (f32)nsingles / nmaxsingles * 100.0f); 224 | 225 | printf("%" fu16 " timing singletaps (%g%%)\n", 226 | nsingles_timing, (f32)nsingles_timing / nmaxsingles * 100.0f); 227 | 228 | printf("%" fu16 " notes within singletap threshold (%g%%)\n", 229 | nsingles_threshold, 230 | (f32)nsingles_threshold / nmaxsingles * 100.0f); 231 | 232 | printf("\n%gpp\n", pp); 233 | } 234 | 235 | // json output 236 | internalfn 237 | void print_escaped_json_string(FILE* fd, const char* str) 238 | { 239 | putchar('"'); 240 | 241 | const char* chars_to_escape = "\\\""; 242 | 243 | for (; *str; ++str) 244 | { 245 | // escape all characters in chars_to_escape 246 | for (const char* p = chars_to_escape; *p; ++p) { 247 | if (*p == *str) { 248 | putchar('\\'); 249 | } 250 | } 251 | 252 | putchar(*str); 253 | } 254 | 255 | putchar('"'); 256 | } 257 | 258 | // https://www.doc.ic.ac.uk/%7Eeedwards/compsys/float/nan.html 259 | 260 | bool is_inf(double b) 261 | { 262 | u64* p = (u64*)&b; 263 | return *p == 0x7FF0000000000000LL || 264 | *p == 0xFFF0000000000000LL; 265 | } 266 | 267 | bool is_nan(double b) 268 | { 269 | u64* p = (u64*)&b; 270 | return 271 | (*p > 0x7FF0000000000000LL && *p < 0x8000000000000000LL) || 272 | (*p > 0xFFF7FFFFFFFFFFFFLL && *p <= 0xFFFFFFFFFFFFFFFFLL); 273 | } 274 | 275 | // json is mentally challenged and can't handle inf and nan so we/re gonna be 276 | // mathematically incorrect 277 | void fix_json_f64(double* v) 278 | { 279 | if (is_inf(*v)) { 280 | *v = DBL_MAX; 281 | } 282 | 283 | if (is_nan(*v)) { 284 | *v = 0; 285 | } 286 | } 287 | 288 | internalfn 289 | print_sig(json_print) 290 | { 291 | double dod = b.od; 292 | double dar = b.ar; 293 | double dcs = b.cs; 294 | double dhp = b.hp; 295 | 296 | fix_json_f64(&res.acc_percent); 297 | fix_json_f64(&res.pp); 298 | fix_json_f64(&res.speed_pp); 299 | fix_json_f64(&res.acc_pp); 300 | fix_json_f64(&stars); 301 | fix_json_f64(&aim); 302 | fix_json_f64(&speed); 303 | fix_json_f64(&rhythm_awkwardness); 304 | fix_json_f64(&dod); 305 | fix_json_f64(&dar); 306 | fix_json_f64(&dcs); 307 | fix_json_f64(&dhp); 308 | 309 | printf("%s", "{\"oppai_version\":"); 310 | print_escaped_json_string(fd, version_string); 311 | 312 | // first print the artist, title, version and creator like this 313 | // since json-string so " and \ needs to be escaped 314 | printf("%s", ",\"artist\":"); 315 | print_escaped_json_string(fd, b.artist); 316 | printf("%s", ",\"title\":"); 317 | print_escaped_json_string(fd, b.title); 318 | printf("%s", ",\"version\":"); 319 | print_escaped_json_string(fd, b.version); 320 | printf("%s", ",\"creator\":"); 321 | print_escaped_json_string(fd, b.creator); 322 | 323 | // now print the rest 324 | printf( 325 | "," 326 | "\"mods_str\": \"%s\"," 327 | "\"od\":%g,\"ar\":%g,\"cs\":%g,\"hp\":%g," 328 | "\"combo\": %" fu16 ",\"max_combo\": %" fu16 "," 329 | "\"num_circles\": %" fu16 "," 330 | "\"num_sliders\": %" fu16 "," 331 | "\"num_spinners\": %" fu16 "," 332 | "\"misses\": %" fu16 "," 333 | "\"score_version\": %" fu32 "," 334 | "\"stars\": %.17g,\"speed_stars\": %.17g,\"aim_stars\": %.17g,", 335 | 336 | mods_str ? mods_str : "", 337 | (i32)(dod * 100.0) / 100.0, 338 | (i32)(dar * 100.0) / 100.0, 339 | (i32)(dcs * 100.0) / 100.0, 340 | (i32)(dhp * 100.0) / 100.0, 341 | combo, b.max_combo, 342 | b.num_circles, b.num_sliders, b.num_spinners, 343 | misses, scoring, 344 | stars, speed, aim 345 | ); 346 | 347 | if (has_awkwardness) { 348 | printf("\"rhythm_awkwardness\": %.17g,", rhythm_awkwardness); 349 | } 350 | 351 | printf( 352 | "\"nsingles\": %" fu16 "," 353 | "\"nsingles_timing\": %" fu16 "," 354 | "\"nsingles_threshold\": %" fu16 "," 355 | "\"pp\":%.17g" 356 | "}\n", 357 | 358 | nsingles, nsingles_timing, nsingles_threshold, 359 | res.pp 360 | ); 361 | } 362 | 363 | // binary output 364 | internalfn 365 | bool encode_str(FILE* fd, const char* str) 366 | { 367 | u16 len = 0xFFFF; 368 | size_t slen = strlen(str); 369 | 370 | if (slen < 0xFFFF) { 371 | len = (u16)slen; 372 | } 373 | 374 | if (fwrite(&len, 2, 1, fd) != 1) { 375 | perror("fwrite"); 376 | return false; 377 | } 378 | 379 | if (fwrite(str, 1, len, fd) != len) { 380 | perror("fwrite"); 381 | return false; 382 | } 383 | 384 | return true; 385 | } 386 | 387 | // binary format history: 388 | // version 2: added hp 389 | 390 | print_sig(binary_print) 391 | { 392 | if (!freopen(0, "wb", fd)) { 393 | perror("freopen"); 394 | exit(1); 395 | } 396 | 397 | u32 binary_output_version = 2; 398 | 399 | // TODO: shorten this with macros 400 | fputc('\0', fd); 401 | fputc('\0', fd); // is_struct 402 | fwrite(&binary_output_version, 4, 1, fd); 403 | if (!encode_str(fd, version_string) || 404 | !encode_str(fd, b.artist) || 405 | !encode_str(fd, b.title) || 406 | !encode_str(fd, b.version) || 407 | !encode_str(fd, b.creator) || 408 | !encode_str(fd, mods_str ? mods_str : "")) 409 | { 410 | exit(1); 411 | } 412 | 413 | fwrite(&b.od, sizeof(f32), 1, fd); 414 | fwrite(&b.ar, sizeof(f32), 1, fd); 415 | fwrite(&b.cs, sizeof(f32), 1, fd); 416 | fwrite(&b.hp, sizeof(f32), 1, fd); 417 | fwrite(&combo, 2, 1, fd); 418 | fwrite(&b.max_combo, 2, 1, fd); 419 | fwrite(&b.num_circles, 2, 1, fd); 420 | fwrite(&b.num_sliders, 2, 1, fd); 421 | fwrite(&b.num_spinners, 2, 1, fd); 422 | fwrite(&misses, 2, 1, fd); 423 | fwrite(&scoring, 4, 1, fd); 424 | 425 | f32 tmp = (f32)stars; 426 | fwrite(&tmp, sizeof(f32), 1, fd); 427 | 428 | tmp = (f32)speed; 429 | fwrite(&tmp, sizeof(f32), 1, fd); 430 | 431 | tmp = (f32)aim; 432 | fwrite(&tmp, sizeof(f32), 1, fd); 433 | 434 | tmp = (f32)res.pp; 435 | fwrite(&tmp, sizeof(f32), 1, fd); 436 | } 437 | 438 | #ifndef __GNUC__ 439 | #pragma pack(push, 1) 440 | #endif 441 | 442 | // binary struct output 443 | struct binary_output_data 444 | { 445 | u8 must_be_zero; 446 | u8 is_struct; 447 | u32 output_version; 448 | char oppai_version[256]; 449 | char artist[256]; 450 | char title[256]; 451 | char version[256]; 452 | char creator[256]; 453 | char mods_str[256]; 454 | f32 od, ar, cs, hp; 455 | u16 combo, max_combo; 456 | u16 num_circles, num_sliders, num_spinners; 457 | u16 misses; 458 | u32 scoring; 459 | f32 stars, speed, aim; 460 | f32 pp; 461 | } 462 | #ifdef __GNUC__ 463 | __attribute__ ((aligned (1), packed)); 464 | #else 465 | ; 466 | #pragma pack(pop) 467 | #endif 468 | 469 | print_sig(binary_struct_print) 470 | { 471 | if (!freopen(0, "wb", fd)) { 472 | perror(0); 473 | exit(1); 474 | } 475 | 476 | binary_output_data d; 477 | memset(&d, 0, sizeof(binary_output_data)); 478 | 479 | d.is_struct = 1; 480 | d.output_version = 1; 481 | strcpy(d.oppai_version, version_string); 482 | strcpy(d.artist, b.artist); 483 | strcpy(d.title, b.title); 484 | strcpy(d.version, b.version); 485 | strcpy(d.creator, b.creator); 486 | strcpy(d.mods_str, mods_str ? mods_str : ""); 487 | d.od = b.od; 488 | d.ar = b.ar; 489 | d.cs = b.cs; 490 | d.hp = b.hp; 491 | d.combo = combo; 492 | d.max_combo = b.max_combo; 493 | d.num_circles = b.num_circles; 494 | d.num_sliders = b.num_sliders; 495 | d.num_spinners = b.num_spinners; 496 | d.misses = misses; 497 | d.scoring = scoring; 498 | d.stars = (f32)stars; 499 | d.speed = (f32)speed; 500 | d.aim = (f32)aim; 501 | d.pp = (f32)res.pp; 502 | 503 | fwrite(&d, sizeof(binary_output_data), 1, fd); 504 | } 505 | 506 | // --- 507 | 508 | typedef print_sig(print_callback); 509 | #undef print_sig 510 | #undef printf 511 | #undef puts 512 | #undef putchar 513 | 514 | struct output_module 515 | { 516 | const char* name; 517 | print_callback* print; 518 | }; 519 | 520 | globvar 521 | output_module modules[] = 522 | { 523 | { "text", text_print }, 524 | { "json", json_print }, 525 | { "binary", binary_print }, 526 | { "binary_struct", binary_struct_print }, 527 | { 0, 0 } 528 | }; 529 | 530 | OPPAIAPI 531 | output_module* get_output_modules() { 532 | return modules; 533 | } 534 | 535 | OPPAIAPI 536 | output_module* get_output_module(const char* name) 537 | { 538 | for (output_module* m = modules; m->name; ++m) 539 | { 540 | if (!strcmp(m->name, name)) { 541 | return m; 542 | } 543 | } 544 | 545 | return 0; 546 | } 547 | 548 | // ----------------------------------------------------------------------------- 549 | 550 | #ifndef OPPAI_LIB 551 | const size_t bufsize = 8000000; 552 | globvar char buf[bufsize]; 553 | 554 | int main(int argc, char* argv[]) 555 | { 556 | // TODO: abstract error outputting into the output modules 557 | 558 | if (argc < 2) 559 | { 560 | printf("Usage: %s /path/to/difficulty.osu " 561 | "{[acc]%% or [num_100s]x100 [num_50s]x50} +[mods] " 562 | "[combo]x [misses]m scorev[scoring_version] " 563 | "AR[ar_override] OD[od_override] CS[cs_override] " 564 | "[other options]\n\n", *argv); 565 | puts("use \"-\" instead of a path to an .osu file to read from stdin\n"); 566 | puts("acc: the accuracy in percent (example: 99.99%)\n"); 567 | puts("num_100s, num_50s: specify accuracy in 100 and 50 count\n"); 568 | puts("mods: any combination of nomod, nf, ez, hd, hr, dt, ht" 569 | ", nc, fl, so (example: +hddthr)\n"); 570 | puts("combo: the highest combo (example: 1337x)\n"); 571 | puts("misses: amount of misses (example: 1m)\n"); 572 | puts("scoring_version: can only be 1 or 2 (example: scorev2)\n"); 573 | puts("ar_override, od_override, cs_override: overrides base ar/od/cs " 574 | "for the map. useful to quickly calculate how it would affect pp " 575 | "without editing the actual map. (example: AR10 OD10 CS6.5)\n"); 576 | puts("singletap_max_bpm: singletap bpm threshold, defaults to 240. " 577 | "this is used to count how many notes are within 1/2 singletaps " 578 | "of this bpm.\n"); 579 | 580 | printf("output_module: the module that will be used to output the " 581 | "results (defaults to text). currently available modules: "); 582 | 583 | for (output_module* m = get_output_modules(); m->name; ++m) { 584 | printf("%s ", m->name); 585 | } 586 | 587 | puts("\n"); 588 | 589 | puts("-no-awkwardness: disables rhythm awkwardness calculation. since " 590 | "this is disabled by default as of 0.9.8, this flag is only kept " 591 | "for backwards compatibility\n"); 592 | 593 | puts("-awkwardness: enables experimental rhythm awkwardness " 594 | "calculations, ~10% slower\n"); 595 | 596 | puts("-no-cache: disables caching. since caching is disabled by " 597 | "default as of 0.9.7, this flag does nothing and is just kept " 598 | "for backwards compatibility\n"); 599 | 600 | puts("-cache: enables caching of pre-parsed beatmap data to the " 601 | "oppai_cache folder where the oppai binary is located. this " 602 | "usually boosts performance by ~25% on reasonably fast drives.\n" 603 | "WARNING: the cache files might not be portable to different " 604 | "machines and might cause incorrect calculations if corrupt\n"); 605 | 606 | puts("-version: prints version number"); 607 | 608 | puts("arguments in [square brackets] are optional"); 609 | puts("(the order of the optional arguments does not matter)"); 610 | 611 | return 1; 612 | } 613 | 614 | // --- 615 | 616 | #if OPPAI_PROFILING 617 | const int prid = 0; 618 | #endif 619 | 620 | bool no_cache = true; 621 | 622 | if (!strcmp(argv[1], "-version")) { 623 | puts(version_string); 624 | exit(0); 625 | } 626 | 627 | // TODO: find a way to do this without using 2 loops 628 | for (int i = 2; i < argc; i++) 629 | { 630 | // no cache (purely for backwards compatibility) 631 | if (!strcmp(argv[i], "-no-cache")) { 632 | no_cache = true; 633 | argv[i] = 0; 634 | break; 635 | } 636 | 637 | if (!strcmp(argv[i], "-cache")) { 638 | no_cache = false; 639 | argv[i] = 0; 640 | break; 641 | } 642 | } 643 | 644 | profile_init(); 645 | 646 | // --- 647 | 648 | profile(prid, "beatmap parse"); 649 | 650 | oppai_ctx ctx; 651 | beatmap b(&ctx); 652 | beatmap::parse(argv[1], b, buf, bufsize, no_cache); 653 | chk(&ctx); 654 | 655 | // --- 656 | 657 | profile(prid, "arguments parse"); 658 | 659 | char* output_module_name = (char*)"text"; 660 | char* mods_str = 0; 661 | f64 acc = 0; 662 | f32 ar, od, cs; 663 | u32 mods = mods::nomod; 664 | u16 combo = b.max_combo; 665 | u16 misses = 0; 666 | u32 scoring = 1; 667 | u16 c100 = 0, c50 = 0; 668 | i32 single_max_bpm = 240; 669 | 670 | // TODO: bitmask 671 | bool no_percent = true; 672 | bool no_awkwardness = true; 673 | 674 | dbgputs("\nparsing arguments"); 675 | 676 | for (int i = 2; i < argc; i++) 677 | { 678 | char suff[64] = {0}; 679 | char* a = argv[i]; 680 | 681 | if (!a) { 682 | continue; 683 | } 684 | 685 | std::transform(a, a + strlen(a), a, tolower_wrapper); 686 | 687 | if (a[0] == '-') 688 | { 689 | // output module 690 | if (a[1] == 'o') { 691 | output_module_name = a + 2; 692 | continue; 693 | } 694 | 695 | // singletap threshold 696 | i32 tmp; 697 | if (sscanf(a + 1, "st%" fi32, &tmp) == 1) { 698 | single_max_bpm = tmp; 699 | continue; 700 | } 701 | 702 | // no rhythm awkwardness (purely for backwards compatibility) 703 | if (!strcmp(a + 1, "no-awkwardness")) { 704 | no_awkwardness = true; 705 | continue; 706 | } 707 | 708 | // enable rhythm awkwardness 709 | if (!strcmp(a + 1, "awkwardness")) { 710 | no_awkwardness = false; 711 | continue; 712 | } 713 | } 714 | 715 | // acc 716 | f64 tmp_acc; 717 | if (sscanf(a, "%lf%s", &tmp_acc, suff) == 2 && !strcmp(suff, "%")) 718 | { 719 | acc = tmp_acc; 720 | no_percent = false; 721 | continue; 722 | } 723 | 724 | // 100s, 50s 725 | u16 tmp_c100 = 0, tmp_c50 = 0; 726 | if (sscanf(a, "%" fu16 "%s", &tmp_c100, suff) == 2 && 727 | !strcmp(suff, "x100")) 728 | { 729 | c100 = tmp_c100; 730 | continue; 731 | } 732 | 733 | if (sscanf(a, "%" fu16 "%s", &tmp_c50, suff) == 2 && 734 | !strcmp(suff, "x50")) 735 | { 736 | c50 = tmp_c50; 737 | continue; 738 | } 739 | 740 | // mods 741 | char* tmp_mods_str = 0; 742 | 743 | for (size_t j = 0; j < num_mods; j++) 744 | { 745 | if (strstr(a, mod_strs[j])) 746 | { 747 | tmp_mods_str = a; 748 | mods |= mod_masks[j]; 749 | } 750 | } 751 | 752 | if (tmp_mods_str == a && *tmp_mods_str == '+') 753 | { 754 | // at least one mod found in the parameter and the prefix matches 755 | mods_str = tmp_mods_str; 756 | std::transform(mods_str, mods_str + strlen(mods_str), mods_str, 757 | toupper_wrapper); 758 | continue; 759 | } 760 | 761 | // combo 762 | u16 tmp_combo; 763 | 764 | if (sscanf(a, "%" fu16 "%s", &tmp_combo, suff) == 2 && 765 | !strcmp(suff, "x")) 766 | { 767 | combo = tmp_combo; 768 | continue; 769 | } 770 | 771 | // misses 772 | u16 tmp_misses; 773 | if (sscanf(a, "%" fu16 "%s", &tmp_misses, suff) == 2 && 774 | (!strcmp(suff, "xm") || !strcmp(suff, "xmiss") || 775 | !strcmp(suff, "m"))) 776 | { 777 | misses = tmp_misses; 778 | continue; 779 | } 780 | 781 | // scorev1 / scorev2 782 | u32 tmp_scoring; 783 | if (sscanf(a, "scorev%" fu32, &tmp_scoring) == 1) 784 | { 785 | scoring = tmp_scoring; 786 | continue; 787 | } 788 | 789 | // AR/OD/CS override 790 | if (sscanf(a, "ar%f", &ar) == 1) { 791 | b.ar = ar; 792 | continue; 793 | } 794 | 795 | if (sscanf(a, "od%f", &od) == 1) { 796 | b.od = od; 797 | continue; 798 | } 799 | 800 | if (sscanf(a, "cs%f", &cs) == 1) { 801 | b.cs = cs; 802 | continue; 803 | } 804 | 805 | printf(">%s\n", a); 806 | die(&ctx, "Invalid parameter"); 807 | break; 808 | } 809 | 810 | chk(&ctx); 811 | 812 | // --- 813 | 814 | #ifdef SHOW_BEATMAP 815 | print_beatmap(b, &ctx); 816 | chk(&ctx); 817 | #endif 818 | 819 | profile(prid, "diff calc"); 820 | b.apply_mods(mods); 821 | chk(&ctx); 822 | 823 | d_calc_ctx dctx(&ctx); 824 | 825 | u16 nsingles = 0, nsingles_timing = 0, nsingles_threshold = 0; 826 | f64 aim = 0, speed = 0, rhythm_complexity = 0; 827 | 828 | f64 stars = 829 | d_calc( 830 | &dctx, 831 | b, 832 | &aim, 833 | &speed, 834 | no_awkwardness ? 0 : &rhythm_complexity, 835 | &nsingles, 836 | &nsingles_timing, 837 | &nsingles_threshold, 838 | (i32)((60000.0f / single_max_bpm) / 2) 839 | ); 840 | chk(&ctx); 841 | 842 | pp_calc_result res = no_percent ? 843 | pp_calc( 844 | &ctx, 845 | aim, speed, b, mods, 846 | combo, misses, 847 | 0xFFFF, c100, c50, 848 | scoring 849 | ) 850 | : pp_calc_acc(&ctx, aim, speed, b, acc, mods, combo, misses, scoring); 851 | 852 | chk(&ctx); 853 | 854 | // --- 855 | 856 | profile(prid, "output"); 857 | output_module* m = get_output_module(output_module_name); 858 | if (!m) { 859 | die(&ctx, "The specified output module does not exist"); 860 | } 861 | chk(&ctx); 862 | 863 | m->print(stdout, mods_str, combo, misses, scoring, 864 | stars, aim, speed, !no_awkwardness, rhythm_complexity, 865 | nsingles, nsingles_timing, nsingles_threshold, res, b); 866 | 867 | // --- 868 | 869 | profile(prid, ""); 870 | 871 | profile_end(); 872 | 873 | return 0; 874 | } 875 | #endif 876 | 877 | #ifdef SHOW_BEATMAP 878 | internalfn 879 | void print_beatmap(beatmap& b, oppai_ctx* ctx) 880 | { 881 | printf( 882 | "Format version: %" fi32 "\n" 883 | "Stack Leniency: %g\n" 884 | "Mode: %" fi32 "\n" 885 | "Title: %s\n" 886 | "Artist: %s\n" 887 | "Version: %s\n" 888 | "HP%g CS%g OD%g AR%g SV%g\n\n" 889 | , 890 | b.format_version, 891 | b.stack_leniency, 892 | b.mode, 893 | b.title, 894 | b.artist, 895 | b.version, 896 | b.hp, b.cs, b.od, b.ar, b.sv 897 | ); 898 | 899 | printf("> %" fu32 " timing points\n", (u32)b.num_timing_points); 900 | 901 | for (size_t i = 0; i < b.num_timing_points; i++) 902 | { 903 | timing_point& tp = b.timing_points[i]; 904 | printf("%" fi32 ": ", tp.time); 905 | 906 | if (!tp.inherit) { 907 | printf("%g bpm\n", 60000.0 / tp.ms_per_beat); 908 | } else { 909 | printf("%gx\n", -100.0 / tp.ms_per_beat); 910 | } 911 | } 912 | 913 | printf("\n> %" fu32 " hit objects\n", (u32)b.num_objects); 914 | 915 | for (size_t i = 0; i < b.num_objects; i++) 916 | { 917 | hit_object& ho = b.objects[i]; 918 | switch (ho.type) { 919 | case obj::circle: 920 | printf("%" fi32 ": Circle (%g, %g)\n", 921 | ho.time, ho.pos.x, ho.pos.y); 922 | break; 923 | 924 | case obj::spinner: 925 | printf("%" fi32 "-%" fi32 ": Spinner\n", 926 | ho.time, ho.end_time); 927 | break; 928 | 929 | case obj::slider: 930 | { 931 | slider_data& sl = ho.slider; 932 | 933 | printf( 934 | "%" fi32 "-%" fi32 ": Slider " 935 | "[Type %c, Length %g, %" fu16 " Repetitions] ", 936 | ho.time, ho.end_time, sl.type, 937 | sl.length, sl.repetitions); 938 | 939 | puts(""); 940 | 941 | break; 942 | } 943 | 944 | default: 945 | die(ctx, "Invalid object type"); 946 | return; 947 | } 948 | } 949 | } 950 | #endif 951 | 952 | -------------------------------------------------------------------------------- /math.cc: -------------------------------------------------------------------------------- 1 | class v2f 2 | { 3 | public: 4 | f32 x, y; 5 | 6 | #if defined(_DEBUG) || defined(SHOW_BEATMAP) 7 | #define i() memset(buf, 0, sizeof(buf)) 8 | #else 9 | #define i() 10 | #endif 11 | 12 | v2f(f32 x, f32 y) : x(x), y(y) { i(); } 13 | v2f() : x(0), y(0) { i(); } 14 | v2f(f32 v) : x(v), y(v) { i(); } 15 | #undef i 16 | 17 | #if defined(_DEBUG) || defined(SHOW_BEATMAP) 18 | const char* str() 19 | { 20 | sprintf(buf, "(%g %g)", x, y); 21 | return buf; 22 | } 23 | #endif 24 | 25 | f32 len() const { 26 | return sqrt(x * x + y * y); 27 | } 28 | 29 | #define do_op(o) \ 30 | inline void operator o##= (const v2f& v) { x o##= v.x; y o##= v.y; } \ 31 | inline void operator o##= (f32 f) { x o##= f; y o##= f; } \ 32 | v2f operator o (const v2f& v) const { return v2f(x o v.x, y o v.y); } \ 33 | v2f operator o (f32 f) const { return v2f(x o f, y o f); } 34 | 35 | do_op(+) 36 | do_op(-) 37 | do_op(*) 38 | do_op(/) 39 | 40 | #undef do_op 41 | 42 | #if defined(_DEBUG) || defined(SHOW_BEATMAP) 43 | protected: 44 | // this is used for formatting with str() 45 | // without having to pass copies of the string around 46 | // obviously not thread safe 47 | static char buf[42]; 48 | }; 49 | 50 | char v2f::buf[42]; 51 | #else 52 | }; 53 | #endif 54 | 55 | -------------------------------------------------------------------------------- /node-oppai/.npmignore: -------------------------------------------------------------------------------- 1 | oppai_cache 2 | build 3 | -------------------------------------------------------------------------------- /node-oppai/README.md: -------------------------------------------------------------------------------- 1 | Node bindings for oppai, a ppv2 and difficulty calculator for osu!. 2 | 3 | Tested on node v7.0.0 on linux, should work on windows as long as you have a 4 | compiler that npm can use. At some point I'll include pre-built windows 5 | binaries. 6 | 7 | # Installing 8 | ```bash 9 | mkdir myproject 10 | cd myproject 11 | npm install oppai 12 | # write your index.js 13 | ``` 14 | 15 | If you're on windows, you might need to set up build tools: 16 | 17 | ```bash 18 | npm install --global --production windows-build-tools 19 | ``` 20 | 21 | If you get compilation errors, try updating node to at least the latest LTS 22 | version (I use 7.0.0). 23 | 24 | If it's still broken, hit me up on github issues and specify your operating 25 | system, ```node -v``` and full output of ```npm install oppai```. 26 | 27 | # Usage 28 | Here's a rather minimal example that parses a beatmap and calculates difficulty 29 | and pp. 30 | 31 | You can find full documentation here: 32 | [Markdown Documentation](https://github.com/Francesco149/oppai/blob/master/node-oppai/docs/README.md). 33 | 34 | ```javascript 35 | const path = require('path'); 36 | const util = require('util'); 37 | const oppai = require('oppai'); 38 | 39 | function main() 40 | { 41 | process.argv.splice(0, 1); 42 | 43 | if (process.argv.length != 2) 44 | { 45 | console.error("Usage: " + process.argv[0] + " file.osu"); 46 | process.exit(1); 47 | } 48 | 49 | var script_path = path.dirname(process.argv[0]); 50 | 51 | // if you need to multithread, create one ctx and buffer for each thread 52 | var ctx = oppai.Ctx(); 53 | 54 | // parse beatmap ----------------------------------------------------------- 55 | var b = oppai.Beatmap(ctx); 56 | 57 | const BUFSIZE = 2000000; // should be big enough to hold the .osu file 58 | var buf = oppai.Buffer(BUFSIZE); 59 | 60 | b.parse( 61 | process.argv[1], 62 | buf, 63 | BUFSIZE, 64 | 65 | // don't disable caching and use js script's folder for caching 66 | false, 67 | script_path 68 | ); 69 | 70 | console.log("Cache folder: " + script_path + "\n"); 71 | 72 | console.log( 73 | util.format( 74 | "%s - %s [%s] (by %s)\n" + 75 | "CS%d OD%d AR%d HP%d\n" + 76 | "%d objects (%d circles, %d sliders, %d spinners)\n" + 77 | "max combo: %d", 78 | b.artist(), b.title(), b.version(), b.creator(), 79 | b.cs().toPrecision(2), b.od().toPrecision(2), 80 | b.ar().toPrecision(2), b.hp().toPrecision(2), 81 | b.numObjects(), b.numCircles(), b.numSliders(), b.numSpinners(), 82 | b.maxCombo() 83 | ) 84 | ); 85 | 86 | // diff calc --------------------------------------------------------------- 87 | dctx = oppai.DiffCalcCtx(ctx); 88 | diff = dctx.diffCalc(b); 89 | 90 | console.log( 91 | util.format( 92 | "\n%d stars\n%d aim stars\n%d speed stars", 93 | diff.stars, diff.aim, diff.speed 94 | ) 95 | ) 96 | 97 | // pp calc ----------------------------------------------------------------- 98 | res = ctx.ppCalc(diff.aim, diff.speed, b); 99 | 100 | console.log( 101 | util.format( 102 | "\n%d aim\n%d speed\n%d acc\n%d pp\nfor %d%%", 103 | res.aimPp, res.speedPp, res.accPp, res.pp, res.accPercent 104 | ) 105 | ); 106 | } 107 | 108 | main(); 109 | ``` 110 | -------------------------------------------------------------------------------- /node-oppai/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'oppai', 5 | 'sources': [ 6 | 'node-oppai.cc' 7 | ], 8 | #'defines': [ 'OPPAI_MODULE_DEBUG=1' ] 9 | 10 | 'conditions': [ 11 | [ 12 | 'OS=="win"', 13 | { 14 | 'defines': ['NOMINMAX=1', '_CRT_SECURE_NO_WARNINGS=1'], 15 | 'cflags_cc+': ['-F8000000', '-wd4201', '-wd4100'], 16 | } 17 | ] 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /node-oppai/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf oppaisrc/ 4 | mkdir oppaisrc/ 5 | cp ../*.cc ../*.hh oppaisrc/ 6 | npm install 7 | -------------------------------------------------------------------------------- /node-oppai/docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [Ctx](#ctx) 6 | - [err](#err) 7 | - [ppCalc](#ppcalc) 8 | - [ppCalcAcc](#ppcalcacc) 9 | - [Buffer](#buffer) 10 | - [Beatmap](#beatmap) 11 | - [applyMods](#applymods) 12 | - [parse](#parse) 13 | - [DiffCalcCtx](#diffcalcctx) 14 | - [DiffCalc](#diffcalc) 15 | 16 | ## Ctx 17 | 18 | **`oppai.Ctx`** 19 | 20 | Creates a pp calculation context for the current thread. 21 | Each instance should not be used concurrently. 22 | 23 | ### err 24 | 25 | Gets a description of the last error that occurred during oppai 26 | calls. This should be checked after 'Beatmap.parse', 'Ctx.ppCalc', 27 | 'Ctx.ppCalcAcc' and 'DiffCalcCtx.diffCalc'. 28 | 29 | **Examples** 30 | 31 | ```javascript 32 | var ctx = oppai.Ctx(); 33 | var b = oppai.Beatmap(ctx); 34 | var buf = oppai.Buffer(2000000); 35 | b.parse("some_file.osu", buf, 2000000, true); 36 | if (ctx.err()) { 37 | console.error(ctx.err()); 38 | process.exit(1); 39 | } 40 | ``` 41 | 42 | Returns **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** description of the last error or undefined if none 43 | 44 | ### ppCalc 45 | 46 | Calculates ppv2 47 | 48 | **Parameters** 49 | 50 | - `aim` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** aim difficulty (see 'DiffCalcCtx.diffCalc') 51 | - `speed` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** speed difficulty (see 'DiffCalcCtx.diffCalc') 52 | - `b` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)<oppai.Beatmap>** the beatmap with map-modifying 53 | mods already applied 54 | - `usedMods` **uint32?** the mods bitmask 55 | (see 'Beatmap.applyMods') (optional, default `oppai.nomod`) 56 | - `combo` **uint16?** desired combo. 0xFFFF will assume full 57 | combo (optional, default `0xFFFF`) 58 | - `misses` **uint16?** amount of misses (optional, default `0`) 59 | - `c300` **uint16?** amount of 300s. 0xFFFF will automatically 60 | calculate this value based on the number 61 | of misses, 100s and 50s. (optional, default `0xFFFF`) 62 | - `c100` **uint16?** number of 100s (optional, default `0`) 63 | - `c50` **uint16?** number of 50s (optional, default `0`) 64 | - `scoreVersion` **uint32?** 1 or 2, affects accuracy pp (optional, default `1`) 65 | 66 | **Examples** 67 | 68 | ```javascript 69 | var ctx = oppai.Ctx(); 70 | 71 | var b = oppai.Beatmap(ctx); 72 | var buf = oppai.Buffer(2000000); 73 | b.parse("some_file.osu", buf, 2000000, true); 74 | 75 | dctx = oppai.DiffCalcCtx(ctx); 76 | diff = dctx.diffCalc(b); 77 | res = ctx.ppCalc(diff.aim, diff.speed, b); 78 | console.log( 79 | util.format( 80 | "\n%d aim\n%d speed\n%d acc\n%d pp\nfor %d%%", 81 | res.aimPp, res.speedPp, res.accPp, res.pp, res.accPercent 82 | ) 83 | ); 84 | ``` 85 | 86 | Returns **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** an object containing 'accPercent', 'pp', 'accPp', 87 | 'speedPp' and 'aimPp'. 88 | 89 | ### ppCalcAcc 90 | 91 | Same as 'Ctx.ppCalc' but uses accuracy percentage instead of number of 92 | 100/50s. Accuracy is automatically rounded to the closest 100/50 count. 93 | 94 | **Parameters** 95 | 96 | - `aim` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** aim difficulty (see 'DiffCalcCtx.diffCalc') 97 | - `speed` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** speed difficulty (see 'DiffCalcCtx.diffCalc') 98 | - `b` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)<oppai.Beatmap>** the beatmap with map-modifying 99 | mods already applied 100 | - `accuracyPercent` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the accuracy in percentage (optional, default `100.0`) 101 | - `usedMods` **uint32?** the mods bitmask 102 | (see 'Beatmap.applyMods') (optional, default `oppai.nomod`) 103 | - `combo` **uint16?** desired combo. 0xFFFF will assume full 104 | combo (optional, default `0xFFFF`) 105 | - `misses` **uint16?** amount of misses (optional, default `0`) 106 | - `scoreVersion` **uint32?** 1 or 2, affects accuracy pp (optional, default `1`) 107 | 108 | **Examples** 109 | 110 | ```javascript 111 | var ctx = oppai.Ctx(); 112 | 113 | var b = oppai.Beatmap(ctx); 114 | var buf = oppai.Buffer(2000000); 115 | b.parse("some_file.osu", buf, 2000000, true); 116 | 117 | dctx = oppai.DiffCalcCtx(ctx); 118 | diff = dctx.diffCalc(b); 119 | res = ctx.ppCalc(diff.aim, diff.speed, b, 95.00); 120 | console.log( 121 | util.format( 122 | "\n%d aim\n%d speed\n%d acc\n%d pp\nfor %d%%", 123 | res.aimPp, res.speedPp, res.accPp, res.pp, res.accPercent 124 | ) 125 | ); 126 | ``` 127 | 128 | Returns **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** an object containing 'accPercent', 'pp', 'accPp', 129 | 'speedPp' and 'aimPp'. 130 | 131 | ## Buffer 132 | 133 | **`oppai.Buffer`** 134 | 135 | Allocates a new buffer to be used for beatmap parsing. 136 | This should be at least as big as the .osu files you're going to 137 | parse. 138 | 139 | **Parameters** 140 | 141 | - `nbytes` **uint32** number of bytes to allocate 142 | 143 | **Examples** 144 | 145 | ```javascript 146 | buf = oppai.Buffer(2000000) 147 | ``` 148 | 149 | ## Beatmap 150 | 151 | **`oppai.Beatmap`** 152 | 153 | Creates an empty Beatmap object. See 'Beatmap.parse' to fill this 154 | object. 155 | 156 | ### applyMods 157 | 158 | Applies map-changing mods. Note that this is currently not reversible 159 | and you will have to re-parse the map to undo mods like DT. 160 | 161 | **Parameters** 162 | 163 | - `usedMods` **uint32** the mods bitmask, which can be built by 164 | bit-wise OR-ing the mod constants 165 | 166 | **Examples** 167 | 168 | ```javascript 169 | var ctx = oppai.Ctx(); 170 | var b = oppai.Beatmap(ctx); 171 | var buf = oppai.Buffer(2000000); 172 | b.parse("some_file.osu", buf, 2000000, true); 173 | b.applyMods(oppai.hd | oppai.hr); 174 | ``` 175 | 176 | ### parse 177 | 178 | Parse .osu file into a 'oppai.Beatmap' object. 179 | 180 | **Parameters** 181 | 182 | - `osuFile` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** path to the .osu file. If "-" is passed, 183 | input will be read from stdin. 184 | - `disableCache` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** by default, beatmaps are cached in a pre-parsed binary format 185 | to disk. Setting this to true disables this feature. (optional, default `false`) 186 | - `customCacheFolder` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** overrides the base cache directory. (optional, default `"path/to/current/executable"`) 187 | 188 | **Examples** 189 | 190 | ```javascript 191 | var ctx = oppai.Ctx(); 192 | var b = oppai.Beatmap(ctx); 193 | var buf = oppai.Buffer(2000000); 194 | b.parse("some_file.osu", buf, 2000000, true); 195 | ``` 196 | 197 | ## DiffCalcCtx 198 | 199 | **`oppai.DiffCalcCtx`** 200 | 201 | Creates a difficulty calculation context for the current thread. 202 | Each instance should not be used concurrently. 203 | 204 | ### DiffCalc 205 | 206 | Calculates overall, aim and speed stars for a map. 207 | 208 | **Parameters** 209 | 210 | - `b` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)<oppai.Beatmap>** a parsed beatmap with map-changing 211 | mods already applied. 212 | - `withRhythmAwkwardness` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** calculates and adds rhythm awkwardness to the returned object. (optional, default `false`) 213 | - `withNSingles` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** calculates and adds the number of spacing singletaps (as seen by 214 | the difficulty calculator) to the returned object. These are 215 | based on spacing thresholds. (optional, default `false`) 216 | - `withNSinglesTiming` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** calculates and adds the number of timing singletaps (1/2 notes) 217 | to the returned object. (optional, default `false`) 218 | - `withNSinglesThreshold` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** calculates and adds the number of notes that are 1/2 or slower at 219 | singletapThreshold BPM. (optional, default `false`) 220 | - `singletapThreshold` **uint32?** singletap threshold BPM for withNSinglesThreshold (optional, default `240`) 221 | 222 | **Examples** 223 | 224 | ```javascript 225 | var ctx = oppai.Ctx(); 226 | 227 | var b = oppai.Beatmap(ctx); 228 | var buf = oppai.Buffer(2000000); 229 | b.parse("some_file.osu", buf, 2000000, true); 230 | 231 | dctx = oppai.DiffCalcCtx(ctx); 232 | diff = dctx.diffCalc(b); 233 | console.log( 234 | util.format( 235 | "\n%d stars\n%d aim stars\n%d speed stars", 236 | diff.stars, diff.aim, diff.speed 237 | ) 238 | ); 239 | ``` 240 | 241 | Returns **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** an object containing 'stars', 'aim', 'speed' and, 242 | optionally, 'rhythmAwkwardness', 'nSingles', 'nSinglesTiming' and 243 | 'nSinglesThreshold'. 244 | -------------------------------------------------------------------------------- /node-oppai/example.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const util = require('util'); 3 | const oppai = require('oppai'); 4 | 5 | function print_pp(res) 6 | { 7 | console.log( 8 | util.format( 9 | "\n%d aim\n%d speed\n%d acc\n%d pp\nfor %d%%", 10 | res.aimPp, res.speedPp, res.accPp, res.pp, res.accPercent 11 | ) 12 | ); 13 | } 14 | 15 | function print_diff(diff) 16 | { 17 | console.log( 18 | util.format( 19 | "\n%d stars\n%d aim stars\n%d speed stars", 20 | diff.stars, diff.aim, diff.speed 21 | ) 22 | ); 23 | } 24 | 25 | function chk(ctx) 26 | { 27 | if (ctx.err()) { 28 | console.error(ctx.err()); 29 | process.exit(1); 30 | } 31 | } 32 | 33 | function main() 34 | { 35 | process.argv.splice(0, 1); 36 | 37 | if (process.argv.length != 2) 38 | { 39 | console.error("Usage: " + process.argv[0] + " file.osu"); 40 | process.exit(1); 41 | } 42 | 43 | var script_path = path.dirname(process.argv[0]); 44 | 45 | // if you need to multithread, create one ctx and buffer for each thread 46 | var ctx = oppai.Ctx(); 47 | 48 | // parse beatmap ----------------------------------------------------------- 49 | var b = oppai.Beatmap(ctx); 50 | 51 | const BUFSIZE = 2000000; // should be big enough to hold the .osu file 52 | var buf = oppai.Buffer(BUFSIZE); 53 | 54 | b.parse( 55 | process.argv[1], 56 | buf, 57 | BUFSIZE, 58 | 59 | // don't disable caching and use js script's folder for caching 60 | false, 61 | script_path 62 | ); 63 | 64 | chk(ctx); 65 | 66 | console.log("Cache folder: " + script_path + "\n"); 67 | 68 | console.log( 69 | util.format( 70 | "%s - %s [%s] (by %s)\n" + 71 | "CS%d OD%d AR%d HP%d\n" + 72 | "%d objects (%d circles, %d sliders, %d spinners)\n" + 73 | "max combo: %d", 74 | b.artist(), b.title(), b.version(), b.creator(), 75 | b.cs().toPrecision(2), b.od().toPrecision(2), 76 | b.ar().toPrecision(2), b.hp().toPrecision(2), 77 | b.numObjects(), b.numCircles(), b.numSliders(), b.numSpinners(), 78 | b.maxCombo() 79 | ) 80 | ); 81 | 82 | // diff calc --------------------------------------------------------------- 83 | dctx = oppai.DiffCalcCtx(ctx); 84 | 85 | diff = dctx.diffCalc(b); 86 | chk(ctx); 87 | 88 | print_diff(diff); 89 | 90 | // pp calc ----------------------------------------------------------------- 91 | res = ctx.ppCalc(diff.aim, diff.speed, b); 92 | chk(ctx); 93 | 94 | print_pp(res); 95 | 96 | // pp calc (with acc %) ---------------------------------------------------- 97 | res = ctx.ppCalcAcc(diff.aim, diff.speed, b, 90.0); 98 | chk(ctx); 99 | 100 | print_pp(res); 101 | 102 | // override OD example ----------------------------------------------------- 103 | console.log("\n----\nIf the map was od10:"); 104 | var oldOd = b.od(); 105 | b.setOd(10); 106 | 107 | res = ctx.ppCalc(diff.aim, diff.speed, b); 108 | chk(ctx); 109 | 110 | print_pp(res); 111 | 112 | b.setOd(oldOd); 113 | 114 | // override AR example ----------------------------------------------------- 115 | console.log("\n----\nIf the map was ar11:"); 116 | var oldAr = b.ar(); 117 | b.setAr(11); 118 | 119 | res = ctx.ppCalc(diff.aim, diff.speed, b); 120 | chk(ctx); 121 | 122 | print_pp(res); 123 | 124 | b.setAr(oldAr); 125 | 126 | // override CS example ----------------------------------------------------- 127 | console.log("\n----\nIf the map was cs6.5:"); 128 | var oldCs = b.cs(); 129 | b.setCs(6.5); 130 | 131 | // remember that CS is map-changing so difficulty must be recomputed 132 | diff = dctx.diffCalc(b); 133 | chk(ctx); 134 | print_diff(diff); 135 | 136 | res = ctx.ppCalc(diff.aim, diff.speed, b); 137 | chk(ctx); 138 | 139 | print_pp(res); 140 | 141 | b.setCs(oldCs); 142 | 143 | // mods example ------------------------------------------------------------ 144 | console.log("\n----\nWith HDHR:"); 145 | 146 | var mods = oppai.hd | oppai.hr; 147 | b.applyMods(mods); 148 | 149 | // mods are map-changing, recompute diff 150 | diff = dctx.diffCalc(b); 151 | chk(ctx); 152 | print_diff(diff); 153 | 154 | res = ctx.ppCalc(diff.aim, diff.speed, b, mods); 155 | chk(ctx); 156 | 157 | print_pp(res); 158 | } 159 | 160 | main(); 161 | -------------------------------------------------------------------------------- /node-oppai/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./build/Release/oppai"); 2 | -------------------------------------------------------------------------------- /node-oppai/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oppai", 3 | "version": "0.1.19", 4 | "description": "pp v2 and difficulty calculator for osu!", 5 | "homepage": "https://github.com/Francesco149/oppai", 6 | "author": { 7 | "name": "Franc[e]sco", 8 | "email": "lolisamurai@tfwno.gf", 9 | "url": "http://weeb.ddns.net/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Francesco149/oppai.git" 14 | }, 15 | "license": "GPL-3.0" 16 | } 17 | -------------------------------------------------------------------------------- /oppai.1: -------------------------------------------------------------------------------- 1 | .TH OPPAI 1 2017-08-12 "oppai 0.9.9" "osu!" 2 | .\" %%%LICENSE_START(GPLv3_DOC_FULL) 3 | .\" This is free documentation; you can redistribute it and/or 4 | .\" modify it under the terms of the GNU General Public License as 5 | .\" published by the Free Software Foundation. 6 | .\" 7 | .\" The GNU General Public License's references to "object code" 8 | .\" and "executables" are to be interpreted as the output of any 9 | .\" document formatting or typesetting system, including 10 | .\" intermediate and printed output. 11 | .\" 12 | .\" This manual is distributed in the hope that it will be useful, 13 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | .\" GNU General Public License for more details. 16 | .\" 17 | .\" You should have received a copy of the GNU General Public 18 | .\" License along with this manual; if not, see 19 | .\" . 20 | .\" %%%LICENSE_END 21 | .SH NAME 22 | oppai \- osu! pp advanced inspector 23 | .SH SYNOPSIS 24 | .P 25 | oppai \fIFILE\fP [\fIOPTION\fP]... 26 | .SH DESCRIPTION 27 | A difficulty and pp calculator for osu! standard beatmaps. It works all maps, 28 | even unranked or unsubmitted ones. All you need to do is supply the map's 29 | \fI*.osu\fP file. 30 | .P 31 | When FILE is \-, read standard input. 32 | .SH OPTIONS 33 | .TP 34 | .IB accuracy % 35 | Set the play accuracy. The default is \fI100%\fP. 36 | .TP 37 | .IB "number of 100s" x100 38 | .TP 39 | .IB "number of 50s" x50 40 | Sets the number of imperfect hits. The default is to assume every note is a 41 | \fI300\fP. 42 | .TP 43 | .BI + mods 44 | Sets which mods were used. When multiple mods are used, simply concatenate 45 | their names, such as "HDHR". The default is to use no mods. 46 | .TP 47 | .IB combo x 48 | Sets the combo. By default full combo is used. 49 | .TP 50 | .BI scorev "version number" 51 | Use the given scoring algorithm instead of the most current one. 52 | .TP 53 | .BI ar "ar override" 54 | Use the given base AR instead of the map's. 55 | .TP 56 | .BI od "od override" 57 | Use the given base OD instead of the map's. 58 | .TP 59 | .BI cs "cs override" 60 | Use the given base CS instead of the map's. 61 | .TP 62 | .BI \-st "max singletap bpm threshold" 63 | This is used to count how many notes are within 1/2 singletaps of this bpm. 64 | Defaults to 240bpm. 65 | .TP 66 | .BI \-o "output module" 67 | How map information is printed to the screen, such as outputting JSON. By 68 | default it is printed as \fItext\fP. 69 | .TP 70 | .BI \-no-awkwardness 71 | -no-awkwardness: disables rhythm awkwardness calculation. since this is disabled 72 | by default as of 0.9.8, this flag is only kept for backwards compatibility 73 | .TP 74 | .BI \-awkwardness 75 | Enables experimental rhythm awkwardness calculations, ~10% slower. 76 | .TP 77 | .BI \-cache 78 | Enables caching of pre-parsed beatmap data to the oppai_cache folder where the 79 | oppai binary is located. 80 | This usually boosts performance by ~25% on reasonably fast drives. 81 | Warning: the cache files might not be portable to different machines and might 82 | cause incorrect calculations if corrupt. 83 | .TP 84 | .BI \-no-cache 85 | Disables caching. Since caching is disabled by default as of 0.9.7, this flag 86 | does nothing and is just kept for backwards compatibility. 87 | .TP 88 | .BI \-version 89 | Prints version number. 90 | .SH RETURN CODES 91 | 0 is returned if the program exited normally, nonzero is returned otherwise. 92 | .SH EXAMPLES 93 | .TP 94 | oppai "gtmn. (witch's slave) - furioso melodia (Alumetorz) [Wrath].osu" 95 | Give scoring and difficulty information about a downloaded map. 96 | .TP 97 | oppai my_map.osu +HR 98% 4x100 98 | Calculate the pp that would earned from a play of the given map with hard rock 99 | (HR), 98% accuracy, and 4 100s. 100 | .TP 101 | curl https://osu.ppy.sh/osu/37658 | oppai - 102 | Download beatmap information from the osu! website and give information on it. 103 | .SH SEE ALSO 104 | \fBosu\fP(1), \fBcurl\fP(1) 105 | -------------------------------------------------------------------------------- /pp_calc.cc: -------------------------------------------------------------------------------- 1 | // turns the beatmaps' strain attributes into a larger value, suitable 2 | // for pp calc. not 100% sure what is going on here, but it probably makes 3 | // strain values scale a bit exponentially. 4 | f64 base_strain(f64 strain) 5 | { 6 | return std::pow(5.0 * std::max(1.0, strain / 0.0675) - 4.0, 3.0) / 7 | 100000.0; 8 | } 9 | 10 | f64 acc_calc(u16 c300, u16 c100, u16 c50, u16 misses) 11 | { 12 | u16 total_hits = c300 + c100 + c50 + misses; 13 | f64 acc = 0.f; 14 | 15 | if (total_hits > 0) 16 | { 17 | acc = ( 18 | c50 * 50.0 + c100 * 100.0 + c300 * 300.0) / 19 | (total_hits * 300.0); 20 | } 21 | 22 | return acc; 23 | } 24 | 25 | struct pp_calc_result 26 | { 27 | f64 acc_percent; 28 | f64 pp; 29 | f64 aim_pp; 30 | f64 speed_pp; 31 | f64 acc_pp; 32 | }; 33 | 34 | // calculates ppv2. 35 | // 36 | // aim: aim difficulty 37 | // speed: speed difficulty 38 | // b: the beatmap with map-modifying mods already applied 39 | // used_mods: the mods bitmask (see namespace mods) 40 | // combo: desired combo. 0xFFFF will assume full combo. 41 | // misses: amount of misses 42 | // c300: amount of 300s. 0xFFFF will automatically calculate this value 43 | // based on the number of misses, 100s and 50s. 44 | // c100, c50: number of 100s and 50s. 45 | // score_version: 1 or 2, affects accuracy pp. 46 | // 47 | // return ppv2 48 | 49 | OPPAIAPI 50 | pp_calc_result 51 | pp_calc( 52 | oppai_ctx* ctx, 53 | f64 aim, f64 speed, beatmap& b, 54 | u32 used_mods=mods::nomod, 55 | u16 combo = 0xFFFF, u16 misses = 0, u16 c300 = 0xFFFF, 56 | u16 c100 = 0, u16 c50 = 0, u32 score_version = 1) 57 | { 58 | pp_calc_result res; 59 | 60 | memset(&res, 0, sizeof(res)); 61 | 62 | f64 od = b.od; 63 | f64 ar = b.ar; 64 | u16 circles = b.num_circles; 65 | 66 | if (c300 == 0xFFFF) { 67 | c300 = (u16)b.num_objects - c100 - c50 - misses; 68 | } 69 | 70 | if (combo == 0xFFFF) { 71 | combo = b.max_combo; 72 | } 73 | 74 | // input validation 75 | if (!b.max_combo) { 76 | b.max_combo = 1; // prevent division by zero 77 | } 78 | 79 | u16 total_hits = c300 + c100 + c50 + misses; 80 | 81 | if (total_hits != b.num_objects) 82 | { 83 | dbgprintf( 84 | "warning: total hits(%" fu16 ") don't match hit-object count " 85 | "(%" fu32 ")\n", 86 | total_hits, (u32)b.num_objects 87 | ); 88 | } 89 | 90 | if (score_version != 1 && score_version != 2) { 91 | die(ctx, "This score version does not exist or isn't supported"); 92 | return res; 93 | } 94 | 95 | // accuracy (not in percentage, ranges between 0 and 1) 96 | f64 acc = acc_calc(c300, c100, c50, misses); 97 | res.acc_percent = acc * 100.0; 98 | 99 | // aim pp ------------------------------------------------------------------ 100 | f64 aim_value = base_strain(aim); 101 | 102 | // length bonus (reused in speed pp) 103 | f64 total_hits_over_2k = (f64)total_hits / 2000.0; 104 | f64 length_bonus = 0.95 + 105 | 0.4 * std::min(1.0, total_hits_over_2k) + 106 | (total_hits > 2000 ? std::log10(total_hits_over_2k) * 0.5 : 0.0); 107 | 108 | // miss penality (reused in speed pp) 109 | f64 miss_penality = std::pow(0.97, misses); 110 | 111 | // combo break penality (reused in speed pp) 112 | f64 combo_break = 113 | std::pow((f64)combo, 0.8) / std::pow((f64)b.max_combo, 0.8); 114 | 115 | aim_value *= length_bonus; 116 | aim_value *= miss_penality; 117 | aim_value *= combo_break; 118 | 119 | f64 ar_bonus = 1.0; 120 | 121 | // high ar bonus 122 | if (ar > 10.33) { 123 | ar_bonus += 0.45 * (ar - 10.33); 124 | } 125 | 126 | // low ar bonus 127 | else if (ar < 8.0) 128 | { 129 | f64 low_ar_bonus = 0.01 * (8.0 - ar); 130 | 131 | if (used_mods & mods::hd) { 132 | low_ar_bonus *= 2.0; 133 | } 134 | 135 | ar_bonus += low_ar_bonus; 136 | } 137 | 138 | aim_value *= ar_bonus; 139 | 140 | // hidden 141 | if (used_mods & mods::hd) { 142 | aim_value *= 1.18; 143 | } 144 | 145 | // flashlight 146 | if (used_mods & mods::fl) { 147 | aim_value *= 1.45 * length_bonus; 148 | } 149 | 150 | // acc bonus (bad aim can lead to bad acc, reused in speed for same reason) 151 | f64 acc_bonus = 0.5 + acc / 2.0; 152 | 153 | // od bonus (low od is easy to acc even with shit aim, reused in speed ...) 154 | f64 od_bonus = 0.98 + std::pow(od, 2) / 2500.0; 155 | 156 | aim_value *= acc_bonus; 157 | aim_value *= od_bonus; 158 | 159 | res.aim_pp = aim_value; 160 | 161 | // speed pp ---------------------------------------------------------------- 162 | f64 speed_value = base_strain(speed); 163 | 164 | speed_value *= length_bonus; 165 | speed_value *= miss_penality; 166 | speed_value *= combo_break; 167 | speed_value *= acc_bonus; 168 | speed_value *= od_bonus; 169 | 170 | res.speed_pp = speed_value; 171 | 172 | // acc pp ------------------------------------------------------------------ 173 | f64 real_acc = 0.0; // accuracy calculation changes from scorev1 to scorev2 174 | 175 | if (score_version == 2) 176 | { 177 | circles = total_hits; 178 | real_acc = acc; 179 | } 180 | 181 | else 182 | { 183 | // scorev1 ignores sliders since they are free 300s 184 | if (circles) { 185 | real_acc = ( 186 | (c300 - (total_hits - circles)) * 300.0 + 187 | c100 * 100.0 + 188 | c50 * 50.0 189 | ) / (circles * 300); 190 | } 191 | 192 | // can go negative if we miss everything 193 | real_acc = std::max(0.0, real_acc); 194 | } 195 | 196 | // arbitrary values tom crafted out of trial and error 197 | f64 acc_value = 198 | std::pow(1.52163, od) * std::pow(real_acc, 24.0) * 2.83; 199 | 200 | // length bonus (not the same as speed/aim length bonus) 201 | acc_value *= std::min(1.15, std::pow(circles / 1000.0, 0.3)); 202 | 203 | // hidden bonus 204 | if (used_mods & mods::hd) { 205 | acc_value *= 1.02; 206 | } 207 | 208 | // flashlight bonus 209 | if (used_mods & mods::fl) { 210 | acc_value *= 1.02; 211 | } 212 | 213 | res.acc_pp = acc_value; 214 | 215 | // total pp ---------------------------------------------------------------- 216 | f64 final_multiplier = 1.12; 217 | 218 | // nofail 219 | if (used_mods & mods::nf) { 220 | final_multiplier *= 0.90; 221 | } 222 | 223 | // spun-out 224 | if (used_mods & mods::so) { 225 | final_multiplier *= 0.95; 226 | } 227 | 228 | res.pp = std::pow( 229 | std::pow(aim_value, 1.1) + 230 | std::pow(speed_value, 1.1) + 231 | std::pow(acc_value, 1.1), 232 | 1.0 / 1.1 233 | ) * final_multiplier; 234 | 235 | return res; 236 | } 237 | 238 | // rounds acc_percent to the closest possible 100-count or 50-count and 239 | // calculates ppv2. 240 | // 241 | // aim: aim difficulty 242 | // speed: speed difficulty 243 | // b: the beatmap with map-modifying mods already applied 244 | // acc_percent: the desired accuracy in percent (0-100) 245 | // used_mods: the mods bitmask (see namespace mods) 246 | // combo: desired combo. 0xFFFF will assume full combo. 247 | // misses: amount of misses 248 | // score_version: 1 or 2, affects accuracy pp. 249 | // 250 | // returns ppv2 251 | 252 | OPPAIAPI 253 | pp_calc_result 254 | pp_calc_acc( 255 | oppai_ctx* ctx, 256 | f64 aim, f64 speed, beatmap& b, f64 acc_percent = 100.0, 257 | u32 used_mods=mods::nomod, u16 combo = 0xFFFF, u16 misses = 0, 258 | u32 score_version = 1) 259 | { 260 | // cap misses to num objects 261 | misses = std::min((u16)b.num_objects, misses); 262 | 263 | // cap acc to max acc with the given amount of misses 264 | u16 max300 = (u16)(b.num_objects - misses); 265 | 266 | acc_percent = std::max(0.0, 267 | std::min(acc_calc(max300, 0, 0, misses) * 100.0, acc_percent)); 268 | 269 | // round acc to the closest amount of 100s or 50s 270 | u16 c50 = 0; 271 | u16 c100 = (u16)macro_round(-3.0 * ((acc_percent * 0.01 - 1.0) * 272 | b.num_objects + misses) * 0.5); 273 | 274 | if (c100 > b.num_objects - misses) 275 | { 276 | // acc lower than all 100s, use 50s 277 | c100 = 0; 278 | c50 = (u16)macro_round(-6.0 * ((acc_percent * 0.01 - 1.0) * 279 | b.num_objects + misses) * 0.2); 280 | 281 | c50 = std::min(max300, c50); 282 | } 283 | else { 284 | c100 = std::min(max300, c100); 285 | } 286 | 287 | u16 c300 = (u16)b.num_objects - c100 - c50 - misses; 288 | 289 | return 290 | pp_calc( 291 | ctx, 292 | aim, speed, b, used_mods, 293 | combo, misses, 294 | c300, c100, c50, 295 | score_version 296 | ); 297 | } 298 | 299 | -------------------------------------------------------------------------------- /profiler.cc: -------------------------------------------------------------------------------- 1 | #if OPPAI_PROFILING 2 | #include 3 | #include 4 | #include 5 | 6 | // profiling is linux-only for now since it's mainly for myself 7 | internalfn 8 | f64 time_now() 9 | { 10 | struct timespec t; 11 | 12 | memset(&t, 0, sizeof(struct timespec)); 13 | 14 | if (clock_gettime(CLOCK_MONOTONIC, &t) < 0) { 15 | perror(0); 16 | exit(1); 17 | } 18 | 19 | return t.tv_sec + t.tv_nsec * (f64)1e-9; 20 | } 21 | 22 | #define MAX_PROFILERS 3 23 | 24 | globvar char const* profile_last_name[MAX_PROFILERS]; 25 | globvar f64 profile_last[MAX_PROFILERS]; 26 | 27 | struct iterations 28 | { 29 | u32 n; 30 | f64 sum; 31 | 32 | iterations() : n(0), sum(0) {} 33 | }; 34 | 35 | // TODO: don't use map 36 | globvar std::map profile_iterations[MAX_PROFILERS]; 37 | 38 | internalfn 39 | void profile_init() { 40 | memset(profile_last_name, 0, sizeof(profile_last_name)); 41 | memset(profile_last, 0, sizeof(profile_last)); 42 | } 43 | 44 | internalfn 45 | void profile(int i, char const* name) 46 | { 47 | if (i > MAX_PROFILERS - 1) { 48 | fprintf(stderr, "bruh fix your profilers\n"); 49 | exit(1); 50 | } 51 | 52 | f64 now = time_now(); 53 | 54 | if (profile_last_name[i]) 55 | { 56 | iterations& it = profile_iterations[i][profile_last_name[i]]; 57 | ++it.n; 58 | it.sum += now - profile_last[i]; 59 | } 60 | 61 | profile_last[i] = now; 62 | profile_last_name[i] = name; 63 | } 64 | 65 | internalfn 66 | void profile_end() 67 | { 68 | for (int i = 0; i < MAX_PROFILERS; ++i) 69 | { 70 | f64 total_time = 0; 71 | 72 | for (std::map::iterator pair = 73 | profile_iterations[i].begin(); 74 | pair != profile_iterations[i].end(); 75 | ++pair) 76 | { 77 | total_time += pair->second.sum; 78 | } 79 | 80 | for (std::map::iterator pair = 81 | profile_iterations[i].begin(); 82 | pair != profile_iterations[i].end(); 83 | ++pair) 84 | { 85 | for (int j = 0; j < i; ++j) { 86 | fprintf(stderr, " "); 87 | } 88 | 89 | fprintf( 90 | stderr, 91 | "PROFILER%d|%s: %gs (%g%%)\n", i, 92 | pair->first.c_str(), pair->second.sum / pair->second.n, 93 | pair->second.sum / total_time * 100.0 94 | ); 95 | } 96 | } 97 | } 98 | #else 99 | #define profile_init() 100 | #define profile(a, b) 101 | #define profile_end() 102 | #endif 103 | -------------------------------------------------------------------------------- /pyoppai/README.md: -------------------------------------------------------------------------------- 1 | Python bindings for oppai. Tested on windows and linux. 2 | 3 | Supports Python 2.7 and 3+. 4 | 5 | # What is this and why should I use it 6 | Bindings are much cleaner and high-performance than spawning an oppai process, 7 | especially if you need to run calculations on thousands of maps. 8 | It also makes it easier to handle errors and customize behaviour beyond what 9 | the command line tool can do. 10 | 11 | # How to install 12 | Make sure you have git to clone this repository. On linux it's usually available 13 | in your package manager if not already installed, while on windows you will need 14 | to install git bash. 15 | 16 | ``` 17 | git clone https://github.com/Francesco149/oppai.git 18 | cd oppai/pyoppai 19 | python setup.py install 20 | ``` 21 | 22 | (use sudo for the last command if you're on Linux). 23 | 24 | # Documentation 25 | Start the python interpreter and run the following to see the full documentation 26 | 27 | ```python 28 | import pyoppai 29 | 30 | help(pyoppai) 31 | ``` 32 | 33 | # Usage 34 | Here's a minimal example of parsing a beatmap, calculating difficulty and 35 | calculating pp. No error checking for brevity. See ```example.py``` for full 36 | error checking and more advanced features. 37 | 38 | ```python 39 | import sys 40 | import os 41 | import pyoppai 42 | 43 | def main(): 44 | if len(sys.argv) != 2: 45 | print("Usage: " + sys.argv[0] + " file.osu") 46 | sys.exit(1) 47 | 48 | ctx = pyoppai.new_ctx() 49 | 50 | # parse beatmap ------------------------------------------------------------ 51 | b = pyoppai.new_beatmap(ctx) 52 | 53 | BUFSIZE = 2000000 # should be big enough to hold the .osu file 54 | buf = pyoppai.new_buffer(BUFSIZE) 55 | 56 | pyoppai.parse( 57 | sys.argv[1], 58 | b, 59 | buf, 60 | BUFSIZE, 61 | 62 | # don't disable caching and use python script's folder for caching 63 | False, 64 | os.path.dirname(os.path.realpath(__file__)) 65 | ); 66 | 67 | # diff calc ---------------------------------------------------------------- 68 | dctx = pyoppai.new_d_calc_ctx(ctx) 69 | 70 | stars, aim, speed, _, _, _, _ = pyoppai.d_calc(dctx, b) 71 | 72 | print( 73 | "%.17g stars\n%.17g aim stars\n%.17g speed stars" % 74 | (stars, aim, speed) 75 | ) 76 | 77 | # pp calc ------------------------------------------------------------------ 78 | acc, pp, aim_pp, speed_pp, acc_pp = \ 79 | pyoppai.pp_calc(ctx, aim, speed, b) 80 | 81 | print( 82 | "\n%.17g aim\n%.17g speed\n%.17g acc\n%.17g pp\nfor %.17g%%" % 83 | (aim_pp, speed_pp, acc_pp, pp, acc) 84 | ) 85 | 86 | main() 87 | ``` 88 | -------------------------------------------------------------------------------- /pyoppai/example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import pyoppai 4 | 5 | def print_pp(acc, pp, aim_pp, speed_pp, acc_pp): 6 | print( 7 | "\n%.17g aim\n%.17g speed\n%.17g acc\n%.17g pp\nfor %.17g%%" % 8 | (aim_pp, speed_pp, acc_pp, pp, acc) 9 | ) 10 | 11 | def print_diff(stars, aim, speed): 12 | print( 13 | "\n%.17g stars\n%.17g aim stars\n%.17g speed stars" % 14 | (stars, aim, speed) 15 | ) 16 | 17 | def chk(ctx): 18 | err = pyoppai.err(ctx) 19 | 20 | if err: 21 | print(err) 22 | sys.exit(1) 23 | 24 | def main(): 25 | if len(sys.argv) != 2: 26 | print("Usage: " + sys.argv[0] + " file.osu") 27 | sys.exit(1) 28 | 29 | # if you need to multithread, create one ctx and buffer for each thread 30 | ctx = pyoppai.new_ctx() 31 | 32 | # parse beatmap ------------------------------------------------------------ 33 | b = pyoppai.new_beatmap(ctx) 34 | 35 | BUFSIZE = 2000000 # should be big enough to hold the .osu file 36 | buf = pyoppai.new_buffer(BUFSIZE) 37 | 38 | pyoppai.parse( 39 | sys.argv[1], 40 | b, 41 | buf, 42 | BUFSIZE, 43 | 44 | # don't disable caching and use python script's folder for caching 45 | False, 46 | os.path.dirname(os.path.realpath(__file__)) 47 | ); 48 | 49 | chk(ctx) 50 | 51 | print("Cache folder: " + os.path.dirname(os.path.realpath(__file__)) + "\n") 52 | 53 | cs, od, ar, hp = pyoppai.stats(b) 54 | 55 | print( 56 | "%s - %s [%s] (by %s)\n" 57 | "CS%g OD%g AR%g HP%g\n" 58 | "%d objects (%d circles, %d sliders, %d spinners)\n" 59 | "max combo: %d" % 60 | ( 61 | pyoppai.artist(b), 62 | pyoppai.title(b), 63 | pyoppai.version(b), 64 | pyoppai.creator(b), 65 | cs, od, ar, hp, 66 | pyoppai.num_objects(b), 67 | pyoppai.num_circles(b), 68 | pyoppai.num_sliders(b), 69 | pyoppai.num_spinners(b), 70 | pyoppai.max_combo(b) 71 | ) 72 | ) 73 | 74 | # diff calc ---------------------------------------------------------------- 75 | dctx = pyoppai.new_d_calc_ctx(ctx) 76 | 77 | stars, aim, speed, _, _, _, _ = pyoppai.d_calc(dctx, b) 78 | chk(ctx) 79 | 80 | print_diff(stars, aim, speed) 81 | 82 | # pp calc ------------------------------------------------------------------ 83 | acc, pp, aim_pp, speed_pp, acc_pp = \ 84 | pyoppai.pp_calc(ctx, aim, speed, b) 85 | 86 | chk(ctx) 87 | 88 | print_pp(acc, pp, aim_pp, speed_pp, acc_pp) 89 | 90 | # pp calc (with acc %) ----------------------------------------------------- 91 | acc, pp, aim_pp, speed_pp, acc_pp = \ 92 | pyoppai.pp_calc_acc(ctx, aim, speed, b, 90.0) 93 | 94 | chk(ctx) 95 | 96 | print_pp(acc, pp, aim_pp, speed_pp, acc_pp) 97 | 98 | # override OD example ------------------------------------------------------ 99 | print("\n----\nIf the map was od10:") 100 | pyoppai.set_od(b, 10) 101 | 102 | acc, pp, aim_pp, speed_pp, acc_pp = \ 103 | pyoppai.pp_calc(ctx, aim, speed, b) 104 | 105 | chk(ctx) 106 | 107 | print_pp(acc, pp, aim_pp, speed_pp, acc_pp) 108 | 109 | pyoppai.set_od(b, od) 110 | 111 | # override AR example ------------------------------------------------------ 112 | print("\n----\nIf the map was ar11:") 113 | pyoppai.set_ar(b, 11) 114 | 115 | acc, pp, aim_pp, speed_pp, acc_pp = \ 116 | pyoppai.pp_calc(ctx, aim, speed, b) 117 | 118 | chk(ctx) 119 | 120 | print_pp(acc, pp, aim_pp, speed_pp, acc_pp) 121 | 122 | pyoppai.set_ar(b, ar) 123 | 124 | # override CS example ------------------------------------------------------ 125 | print("\n----\nIf the map was cs6.5:") 126 | pyoppai.set_cs(b, 6.5) 127 | 128 | # remember that CS is map-changing so difficulty must be recomputed 129 | stars, aim, speed, _, _, _, _ = pyoppai.d_calc(dctx, b) 130 | chk(ctx) 131 | 132 | print_diff(stars, aim, speed) 133 | 134 | acc, pp, aim_pp, speed_pp, acc_pp = \ 135 | pyoppai.pp_calc(ctx, aim, speed, b) 136 | 137 | chk(ctx) 138 | 139 | print_pp(acc, pp, aim_pp, speed_pp, acc_pp) 140 | 141 | pyoppai.set_cs(b, cs) 142 | 143 | # mods example ------------------------------------------------------------- 144 | print("\n----\nWith HDHR:") 145 | 146 | # mods are a bitmask, same as what the osu! api uses 147 | mods = pyoppai.hd | pyoppai.hr 148 | pyoppai.apply_mods(b, mods) 149 | 150 | # mods are map-changing, recompute diff 151 | stars, aim, speed, _, _, _, _ = pyoppai.d_calc(dctx, b) 152 | chk(ctx) 153 | 154 | print_diff(stars, aim, speed) 155 | 156 | acc, pp, aim_pp, speed_pp, acc_pp = \ 157 | pyoppai.pp_calc(ctx, aim, speed, b, mods) 158 | 159 | chk(ctx) 160 | 161 | print_pp(acc, pp, aim_pp, speed_pp, acc_pp) 162 | 163 | main() 164 | -------------------------------------------------------------------------------- /pyoppai/pyoppaimodule.cc: -------------------------------------------------------------------------------- 1 | #include "../main.cc" 2 | #include 3 | 4 | #ifdef OPPAI_MODULE_DEBUG 5 | #define pydbgprintf(fmt, ...) \ 6 | fprintf(stderr, "DEBUG|pyoppai|"); \ 7 | fprintf(stderr, fmt, __VA_ARGS__) 8 | #else 9 | #define pydbgprintf(fmt, ...) 10 | #endif 11 | 12 | #define pydbgputs(msg) pydbgprintf("%s\n", msg) 13 | 14 | // TODO: don't use new/malloc and pre-allocate a block of memory to use as a 15 | // stack/custom allocator instead 16 | 17 | extern "C" 18 | { 19 | // ------------------------------------------------------------------------- 20 | 21 | #define destructor(tname) \ 22 | internalfn \ 23 | void tname##_free(PyObject* capsule) \ 24 | { \ 25 | tname* p = (tname*) \ 26 | PyCapsule_GetPointer(capsule, "__CPP_API_" #tname "__"); \ 27 | \ 28 | if (!p) { \ 29 | pydbgputs("WARNING|destructor called on null " #tname); \ 30 | return; \ 31 | } \ 32 | \ 33 | pydbgprintf("deallocating " #tname " %p\n", p); \ 34 | \ 35 | delete p; \ 36 | } 37 | 38 | #define encapsulate(tname, p) \ 39 | PyCapsule_New( \ 40 | (void*)p, \ 41 | "__CPP_API_" #tname "__", \ 42 | tname##_free \ 43 | ); 44 | 45 | #define unencapsulate(tname, p) \ 46 | (tname*)PyCapsule_GetPointer(p, "__CPP_API_" #tname "__") 47 | 48 | // ------------------------------------------------------------------------- 49 | 50 | destructor(oppai_ctx) 51 | 52 | internalfn 53 | PyObject* pyoppai_new_ctx(PyObject* self, PyObject* args) 54 | { 55 | oppai_ctx* ctx = new oppai_ctx; 56 | pydbgprintf("allocated oppai_ctx %p\n", ctx); 57 | return encapsulate(oppai_ctx, ctx); 58 | } 59 | 60 | // ------------------------------------------------------------------------- 61 | 62 | internalfn 63 | PyObject* pyoppai_err(PyObject* self, PyObject* args) 64 | { 65 | PyObject* capsule; 66 | 67 | if (!PyArg_ParseTuple(args, "O", &capsule)) { 68 | return 0; 69 | } 70 | 71 | oppai_ctx* p = unencapsulate(oppai_ctx, capsule); 72 | 73 | char const* str = ""; 74 | 75 | if (oppai_err(p)) { 76 | str = oppai_err(p); 77 | } 78 | 79 | return Py_BuildValue("s", str); 80 | } 81 | 82 | // ------------------------------------------------------------------------- 83 | 84 | destructor(beatmap) 85 | 86 | internalfn 87 | PyObject* pyoppai_new_beatmap(PyObject* self, PyObject* args) 88 | { 89 | PyObject* capsule; 90 | 91 | if (!PyArg_ParseTuple(args, "O", &capsule)) { 92 | return 0; 93 | } 94 | 95 | oppai_ctx* ctx = unencapsulate(oppai_ctx, capsule); 96 | beatmap* b = new beatmap(ctx); 97 | pydbgprintf("allocated beatmap %p\n", b); 98 | 99 | return encapsulate(beatmap, b); 100 | } 101 | 102 | // ------------------------------------------------------------------------- 103 | 104 | internalfn 105 | void buffer_free(PyObject* capsule) 106 | { 107 | char* p = (char*) 108 | PyCapsule_GetPointer(capsule, "__CPP_API_oppai_buffer__"); 109 | 110 | if (!p) { 111 | pydbgputs("WANRING|destructor called on empty buffer"); 112 | return; 113 | } 114 | 115 | pydbgprintf("deallocating buffer %p\n", p); 116 | 117 | free(p); 118 | } 119 | 120 | internalfn 121 | PyObject* pyoppai_new_buffer(PyObject* self, PyObject* args) 122 | { 123 | u32 len; 124 | 125 | if (!PyArg_ParseTuple(args, "I", &len)) { 126 | return 0; 127 | } 128 | 129 | char* buf = (char*)malloc(len); 130 | pydbgprintf("allocated buffer %p\n", buf); 131 | 132 | return PyCapsule_New( 133 | (void*)buf, 134 | "__CPP_API_oppai_buffer__", 135 | buffer_free 136 | ); 137 | } 138 | 139 | // ------------------------------------------------------------------------- 140 | 141 | internalfn 142 | PyObject* pyoppai_apply_mods(PyObject* self, PyObject* args) 143 | { 144 | PyObject* capsule; 145 | u32 mods_mask; 146 | 147 | if (!PyArg_ParseTuple(args, "OI", &capsule, &mods_mask)) { 148 | return 0; 149 | } 150 | 151 | beatmap* p = unencapsulate(beatmap, capsule); 152 | p->apply_mods((u32)mods_mask); 153 | 154 | Py_RETURN_NONE; 155 | } 156 | 157 | // ------------------------------------------------------------------------- 158 | 159 | internalfn 160 | PyObject* pyoppai_parse(PyObject* self, PyObject* args) 161 | { 162 | char const* filepath; 163 | PyObject* b_capsule; 164 | PyObject* buf_capsule; 165 | u32 bufsize; 166 | int disable_cache = 0; 167 | char const* custom_cache_path = 0; 168 | 169 | int pres = 170 | PyArg_ParseTuple( 171 | args, "sOOI|is", 172 | &filepath, &b_capsule, &buf_capsule, &bufsize, 173 | &disable_cache, &custom_cache_path 174 | ); 175 | 176 | if (!pres) { 177 | return 0; 178 | } 179 | 180 | beatmap* b = unencapsulate(beatmap, b_capsule); 181 | 182 | char* buf = (char*) 183 | PyCapsule_GetPointer(buf_capsule, "__CPP_API_oppai_buffer__"); 184 | 185 | pydbgprintf( 186 | "beatmap::parse(\"%s\", %p, %p, %" fu32 ", %d, \"%s\")\n", 187 | filepath, b, buf, bufsize, disable_cache, custom_cache_path 188 | ); 189 | 190 | beatmap::parse( 191 | filepath, 192 | *b, 193 | buf, 194 | bufsize, 195 | disable_cache != 0, 196 | custom_cache_path 197 | ); 198 | 199 | Py_RETURN_NONE; 200 | } 201 | 202 | // ------------------------------------------------------------------------- 203 | 204 | destructor(d_calc_ctx) 205 | 206 | internalfn 207 | PyObject* pyoppai_new_d_calc_ctx(PyObject* self, PyObject* args) 208 | { 209 | PyObject* capsule; 210 | 211 | if (!PyArg_ParseTuple(args, "O", &capsule)) { 212 | return 0; 213 | } 214 | 215 | oppai_ctx* ctx = unencapsulate(oppai_ctx, capsule); 216 | d_calc_ctx* dctx = new d_calc_ctx(ctx); 217 | pydbgprintf("allocated d_calc_ctx %p\n", dctx); 218 | 219 | return encapsulate(d_calc_ctx, dctx); 220 | } 221 | 222 | // ------------------------------------------------------------------------- 223 | 224 | internalfn 225 | PyObject* pyoppai_d_calc(PyObject* self, PyObject* args) 226 | { 227 | PyObject* ctx_capsule; 228 | PyObject* b_capsule; 229 | int with_awkwardness = 0; 230 | int with_aim_singles = 0; 231 | int with_timing_singles = 0; 232 | int with_threshold_singles = 0; 233 | i32 singletap_threshold = 125; 234 | 235 | int ptres = 236 | PyArg_ParseTuple( 237 | args, 238 | "OO|iiiii", 239 | &ctx_capsule, 240 | &b_capsule, 241 | &with_awkwardness, 242 | &with_aim_singles, 243 | &with_timing_singles, 244 | &with_threshold_singles, 245 | &singletap_threshold 246 | ); 247 | 248 | if (!ptres) { 249 | return 0; 250 | } 251 | 252 | d_calc_ctx* ctx = unencapsulate(d_calc_ctx, ctx_capsule); 253 | beatmap* b = unencapsulate(beatmap, b_capsule); 254 | 255 | f64 aim = 0, speed = 0, rhythm_awkwardness = 0; 256 | u16 nsingles = 0, nsingles_timing = 0, nsingles_threshold = 0; 257 | 258 | f64 stars = 259 | d_calc( 260 | ctx, *b, &aim, &speed, 261 | with_awkwardness ? &rhythm_awkwardness : 0, 262 | with_aim_singles ? &nsingles : 0, 263 | with_timing_singles ? &nsingles_timing : 0, 264 | with_threshold_singles ? &nsingles_threshold : 0, 265 | singletap_threshold 266 | ); 267 | 268 | pydbgprintf( 269 | "d_calc(%p, %p, %p, %p, %p, %p, %p, %p)\n", 270 | ctx, b, &aim, &speed, 271 | with_awkwardness ? &rhythm_awkwardness : 0, 272 | with_aim_singles ? &nsingles : 0, 273 | with_timing_singles ? &nsingles_timing : 0, 274 | with_threshold_singles ? &nsingles_threshold : 0 275 | ); 276 | 277 | return 278 | Py_BuildValue( 279 | "(ddddHHH)", 280 | stars, aim, speed, rhythm_awkwardness, 281 | nsingles, nsingles_timing, nsingles_threshold 282 | ); 283 | } 284 | 285 | // ------------------------------------------------------------------------- 286 | 287 | internalfn 288 | PyObject* pyoppai_pp_calc(PyObject* self, PyObject* args) 289 | { 290 | PyObject* ctx_capsule; 291 | f64 aim, speed; 292 | PyObject* b_capsule; 293 | u32 used_mods = mods::nomod; 294 | u16 combo = 0xFFFF, misses = 0, c300 = 0xFFFF, c100 = 0, c50 = 0; 295 | u32 score_version = 1; 296 | 297 | int ptres = 298 | PyArg_ParseTuple( 299 | args, 300 | "OddO|IHHHHHI", 301 | &ctx_capsule, 302 | &aim, &speed, 303 | &b_capsule, 304 | &used_mods, 305 | &combo, &misses, &c300, &c100, &c50, 306 | &score_version 307 | ); 308 | 309 | if (!ptres) { 310 | return 0; 311 | } 312 | 313 | oppai_ctx* ctx = unencapsulate(oppai_ctx, ctx_capsule); 314 | beatmap* b = unencapsulate(beatmap, b_capsule); 315 | 316 | pp_calc_result res = 317 | pp_calc( 318 | ctx, 319 | aim, speed, 320 | *b, 321 | used_mods, 322 | combo, misses, c300, c100, c50, 323 | score_version 324 | ); 325 | 326 | pydbgprintf( 327 | "pp_calc(%p, %.17g, %.17g, %p, %08X, " 328 | "%" fu16 ", %" fu16 ", %" fu16 ", %" fu16 "," 329 | " %" fu16 ", %" fu32 ")\n", 330 | ctx, 331 | aim, speed, 332 | b, 333 | used_mods, 334 | combo, misses, c300, c100, c50, 335 | score_version 336 | ); 337 | 338 | return 339 | Py_BuildValue( 340 | "(ddddd)", 341 | res.acc_percent, res.pp, res.aim_pp, res.speed_pp, res.acc_pp 342 | ); 343 | } 344 | 345 | // ------------------------------------------------------------------------- 346 | 347 | internalfn 348 | PyObject* pyoppai_pp_calc_acc(PyObject* self, PyObject* args) 349 | { 350 | PyObject* ctx_capsule; 351 | f64 aim, speed; 352 | PyObject* b_capsule; 353 | f64 acc_percent = 100.0; 354 | u32 used_mods = mods::nomod; 355 | u16 combo = 0xFFFF, misses = 0; 356 | u32 score_version = 1; 357 | 358 | int ptres = 359 | PyArg_ParseTuple( 360 | args, 361 | "OddO|dIHHI", 362 | &ctx_capsule, 363 | &aim, &speed, 364 | &b_capsule, 365 | &acc_percent, 366 | &used_mods, 367 | &combo, &misses, 368 | &score_version 369 | ); 370 | 371 | if (!ptres) { 372 | return 0; 373 | } 374 | 375 | oppai_ctx* ctx = unencapsulate(oppai_ctx, ctx_capsule); 376 | beatmap* b = unencapsulate(beatmap, b_capsule); 377 | 378 | pp_calc_result res = 379 | pp_calc_acc( 380 | ctx, 381 | aim, speed, 382 | *b, 383 | acc_percent, 384 | used_mods, 385 | combo, misses, 386 | score_version 387 | ); 388 | 389 | pydbgprintf( 390 | "pp_calc_acc(%p, %.17g, %.17g, %p, %.17g" 391 | "%" fu16 ", %" fu16 ", %" fu32 ")\n", 392 | ctx, 393 | aim, speed, 394 | b, 395 | acc_percent, 396 | used_mods, 397 | combo, misses, 398 | score_version 399 | ); 400 | 401 | return 402 | Py_BuildValue( 403 | "(ddddd)", 404 | res.acc_percent, res.pp, res.aim_pp, res.speed_pp, res.acc_pp 405 | ); 406 | } 407 | 408 | // ------------------------------------------------------------------------- 409 | 410 | #define getter(tname, name, return_fmt, ...) \ 411 | internalfn \ 412 | PyObject* pyoppai_##name (PyObject* self, PyObject* args) \ 413 | { \ 414 | PyObject* capsule; \ 415 | \ 416 | if (!PyArg_ParseTuple(args, "O", &capsule)) { \ 417 | return 0; \ 418 | } \ 419 | \ 420 | tname* p = unencapsulate(tname, capsule); \ 421 | \ 422 | return Py_BuildValue(return_fmt, __VA_ARGS__); \ 423 | } 424 | 425 | #define sgetter(tname, name, fmt) getter(tname, name, fmt, p->name) 426 | 427 | getter(beatmap, stats, "(ffff)", p->cs, p->od, p->ar, p->hp) 428 | 429 | sgetter(beatmap, mode, "b") 430 | sgetter(beatmap, num_circles, "H") 431 | sgetter(beatmap, num_sliders, "H") 432 | sgetter(beatmap, num_spinners, "H") 433 | sgetter(beatmap, max_combo, "H") 434 | sgetter(beatmap, num_objects, "H") 435 | sgetter(beatmap, title, "s") 436 | sgetter(beatmap, artist, "s") 437 | sgetter(beatmap, creator, "s") 438 | sgetter(beatmap, version, "s") 439 | 440 | // ------------------------------------------------------------------------- 441 | 442 | #define setter(tname, name, vtype, vtype_specifier) \ 443 | internalfn \ 444 | PyObject* pyoppai_set_##name (PyObject* self, PyObject* args) \ 445 | { \ 446 | PyObject* capsule; \ 447 | vtype v; \ 448 | \ 449 | if (!PyArg_ParseTuple(args, "O" vtype_specifier, &capsule, &v)) { \ 450 | return 0; \ 451 | } \ 452 | \ 453 | tname* p = unencapsulate(tname, capsule); \ 454 | p->name = v; \ 455 | \ 456 | Py_RETURN_NONE; \ 457 | } 458 | 459 | #define fsetter(tname, name) setter(tname, name, f32, "f") 460 | 461 | fsetter(beatmap, cs) 462 | fsetter(beatmap, od) 463 | fsetter(beatmap, ar) 464 | 465 | // ------------------------------------------------------------------------- 466 | 467 | PyDoc_STRVAR( 468 | err_doc, 469 | "Return a description of the last error, or an empty string if there\n" 470 | "was no error\n\n" 471 | 472 | "Arguments: (ctx)\n\n" 473 | 474 | "ctx: oppai context object (see new_ctx)" 475 | ); 476 | 477 | PyDoc_STRVAR( 478 | new_beatmap_doc, 479 | "Create an empty beatmap object\n\n" 480 | 481 | "Arguments: (ctx)\n\n" 482 | 483 | "ctx: oppai context object (see new_ctx)" 484 | ); 485 | 486 | PyDoc_STRVAR( 487 | new_buffer_doc, 488 | "Allocates a new buffer for the beatmap parser\n\n" 489 | 490 | "Arguments: (nbytes)\n\n" 491 | 492 | "nbytes: the size in bytes of the buffer. should be at least as big\n" 493 | " as the .osu file" 494 | ); 495 | 496 | PyDoc_STRVAR( 497 | parse_doc, 498 | "Parses a .osu file into a beatmap object\n\n" 499 | 500 | "Arguments:\n" 501 | "(filepath, beatmap, buf, bufsize, disable_cache=False,\n" 502 | " cache_path=)\n\n" 503 | 504 | "filepath: path to the .osu file, or \"-\" to read from stdin\n" 505 | "beatmap: beatmap object (see new_beatmap)\n" 506 | "buf: buffer object. must be at least as big as the\n" 507 | " .osu file\n" 508 | "bufsize: size of the buffer\n" 509 | "disable_cache: by default, beatmaps are cached as pre-parsed binary\n" 510 | " files on disk. setting this to True disables this\n" 511 | " feature\n" 512 | "cache_path: by default, beatmaps are cached in the path of the\n" 513 | " current executable. because python is interpreted,\n" 514 | " this would lead to the interpreter's path. pass your\n" 515 | " script's path here or your preferred cache path" 516 | ); 517 | 518 | PyDoc_STRVAR( 519 | apply_mods_doc, 520 | "Applies map-changing mods to a beatmap\n\n" 521 | 522 | "Arguments: (mods_bitmask)\n\n" 523 | 524 | "mods_bitmask: any combination of the mod constants bit-wise OR-ed\n" 525 | " together (example: pyoppai.hd | pyoppai.hr)" 526 | ); 527 | 528 | PyDoc_STRVAR( 529 | new_d_calc_ctx_doc, 530 | "Create a new difficulty calculation context\n\n" 531 | 532 | "Arguments: (ctx)\n\n" 533 | 534 | "ctx: oppai context object (see new_ctx)" 535 | ); 536 | 537 | PyDoc_STRVAR( 538 | d_calc_doc, 539 | "Calculates difficulty (star rating) for a beatmap\n\n" 540 | 541 | "Arguments:\n" 542 | "(ctx, beatmap, with_awkwardness=False, with_aim_singles=False,\n" 543 | " with_timing_singles=False, with_threshold_singles=False,\n" 544 | " singletap_threshold=125)\n\n" 545 | 546 | "ctx: oppai context object (see new_ctx)\n" 547 | "beatmap: beatmap object (see new_beatmap)\n" 548 | "with_awkwardness: if True, rhythm awkwardness will be\n" 549 | " calculated. otherwise, the 4th tuple\n" 550 | " element returned will be undefined and\n " 551 | " should be ignored with _\n" 552 | "with_aim_singles: if True, the number of aim singletaps will\n" 553 | " be calculated. aim singletaps are as seen by\n" 554 | " are difficulty calculator (based on a\n" 555 | " spacing threshold). if False, the 5th tuple\n" 556 | " element returned will be undefined and\n" 557 | " should beignored with _\n" 558 | "with_timing_singles: if True, the number of 1/2 notes will be\n" 559 | " calculated. otherwise, the 6th tuple\n" 560 | " element returned will be undefined and\n" 561 | " be ignored with _\n" 562 | "with_threshold_singles: if True, the number of notes that are 1/2 or\n" 563 | " slower at singletap_threshold ms will be\n" 564 | " calculated. otherwise, the 6th tuple element\n" 565 | " returned will be undefined and should be\n" 566 | " ignored with _\n" 567 | "\n" 568 | "Returns tuple:\n" 569 | "(stars, aim, speed, rhythm_awkwardness,\n" 570 | " naim_singles, ntiming_singles, nthreshold_singles)" 571 | ); 572 | 573 | PyDoc_STRVAR( 574 | pp_calc_doc, 575 | "Calculates ppv2 for a beatmap\n\n" 576 | 577 | "Arguments:\n" 578 | "(ctx, aim, speed, beatmap, mods_bitmask=pyoppai.nomod, combo=0xFFFF,\n" 579 | " misses=0, c300=0xFFFF, c100=0, c50=0, score_version=1)\n\n" 580 | 581 | "ctx: oppai context object (see new_ctx)\n" 582 | "aim: aim stars (see d_calc)\n" 583 | "speed: speed stars (see d_calc)\n" 584 | "beatmap: beatmap object (see new_beatmap)\n" 585 | "mods_bitmask: any combination of the mod constants bit-wise OR-ed\n" 586 | " together (example: pyoppai.hd | pyoppai.hr)\n" 587 | "combo: passing 0xFFFF defaults to the map's max combo\n" 588 | "score_version: 1 or 2. the default is 1. scorev2 affects accuracy pp\n" 589 | "\n" 590 | "Returns tuple (acc_percent, pp, aim_pp, speed_pp, acc_pp)" 591 | ); 592 | 593 | PyDoc_STRVAR( 594 | pp_calc_acc_doc, 595 | "Same as pp_calc but uses percentage accuracy\n\n" 596 | 597 | "Arguments:\n" 598 | "(ctx, aim, speed, beatmap, acc_percent=100.0,\n" 599 | " mods_bitmask=pyoppai.nomod, combo=0xFFFF, misses=0, score_version=1)" 600 | "\n\n" 601 | 602 | "ctx: oppai context object (see new_ctx)\n" 603 | "aim: aim stars (see d_calc)\n" 604 | "speed: speed stars (see d_calc)\n" 605 | "beatmap: beatmap object (see new_beatmap)\n" 606 | "mods_bitmask: any combination of the mod constants bit-wise OR-ed\n" 607 | " together (example: pyoppai.hd | pyoppai.hr)\n" 608 | "combo: passing 0xFFFF defaults to the map's max combo\n" 609 | "score_version: 1 or 2. the default is 1. scorev2 affects accuracy pp\n" 610 | "\n" 611 | "Returns tuple (acc_percent, pp, aim_pp, speed_pp, acc_pp)" 612 | ); 613 | 614 | // ------------------------------------------------------------------------- 615 | 616 | #define m(name, desc) { #name, pyoppai_##name, METH_VARARGS, desc } 617 | 618 | globvar PyMethodDef pyoppai_methods[] = 619 | { 620 | m(new_ctx, "Create a new oppai context"), 621 | m(err, err_doc), 622 | m(new_beatmap, new_beatmap_doc), 623 | m(new_buffer, new_buffer_doc), 624 | m(parse, parse_doc), 625 | m(apply_mods, apply_mods_doc), 626 | m(new_d_calc_ctx, new_d_calc_ctx_doc), 627 | m(d_calc, d_calc_doc), 628 | m(pp_calc, pp_calc_doc), 629 | m(pp_calc_acc, pp_calc_acc_doc), 630 | 631 | #define g(tname, name, lname) m(name, "Gets a " #tname "'s " lname) 632 | 633 | g(beatmap, stats, "stats (CS, OD, AR, HP) as a tuple"), 634 | g(beatmap, mode, "gamemode"), 635 | g(beatmap, num_circles, "number of circles"), 636 | g(beatmap, num_sliders, "number of sliders"), 637 | g(beatmap, num_spinners, "number of spinners"), 638 | g(beatmap, max_combo, "maximum combo"), 639 | g(beatmap, num_objects, "num. of objects (circles + sliders + spin)"), 640 | g(beatmap, title, "title"), 641 | g(beatmap, artist, "artist"), 642 | g(beatmap, creator, "creator"), 643 | g(beatmap, version, "version (difficulty name)"), 644 | 645 | #undef g 646 | 647 | #define s(tname, name, lname) \ 648 | m(set_##name, "Sets a " #tname "'s " lname) 649 | 650 | s(beatmap, ar, "base approach rate"), 651 | s(beatmap, od, "base overall difficulty"), 652 | s(beatmap, cs, "base circle size"), 653 | 654 | #undef s 655 | 656 | { 0, 0, 0, 0 } 657 | }; 658 | 659 | #undef m 660 | 661 | // ------------------------------------------------------------------------- 662 | 663 | #define PYOPPAI_NAME "pyoppai" 664 | #define PYOPPAI_DESC "ppv2 and difficulty calculator for osu!" 665 | 666 | PyMODINIT_FUNC 667 | PYTHON_MODINIT_FUNC_NAME(pyoppai)() 668 | { 669 | PyObject* m = 670 | python_init_module( 671 | PYOPPAI_NAME, 672 | PYOPPAI_DESC, 673 | pyoppai_methods 674 | ); 675 | 676 | #define mod(name) PyModule_AddIntConstant(m, #name, mods::name) 677 | mod(nomod); 678 | mod(nf); 679 | mod(ez); 680 | mod(hd); 681 | mod(hr); 682 | mod(dt); 683 | mod(ht); 684 | mod(nc); 685 | mod(fl); 686 | mod(so); 687 | #undef mod 688 | 689 | PYTHON_MODINIT_FUNC_RETURN(m); 690 | } 691 | 692 | // ------------------------------------------------------------------------- 693 | } 694 | -------------------------------------------------------------------------------- /pyoppai/python27_pyoppaimodule.cc: -------------------------------------------------------------------------------- 1 | #define python_init_module(name, desc, methods) \ 2 | Py_InitModule3(name, methods, desc) 3 | 4 | #define PYTHON_MODINIT_FUNC_NAME(modname) init##modname 5 | #define PYTHON_MODINIT_FUNC_RETURN(varname) 6 | 7 | #include "pyoppaimodule.cc" 8 | 9 | /* 10 | // example embedded main: 11 | int main(int argc, char* argv[]) 12 | { 13 | Py_SetProgramName(argv[0]); 14 | Py_Initialize(); 15 | PYTHON_MODINIT_FUNC_NAME(pyoppai)(); 16 | 17 | // do shit 18 | 19 | return 0; 20 | } 21 | */ 22 | -------------------------------------------------------------------------------- /pyoppai/python3_pyoppaimodule.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PYTHON_MODINIT_FUNC_NAME(modname) PyInit_##modname 4 | #define PYTHON_MODINIT_FUNC_RETURN(varname) return varname; 5 | 6 | PyObject* python_init_module( 7 | char const* name, 8 | char const* desc, 9 | PyMethodDef* methods) 10 | { 11 | static PyModuleDef moduledef = 12 | { 13 | PyModuleDef_HEAD_INIT, 14 | name, desc, 15 | -1, 16 | methods, 17 | 0, 0, 0, 0 18 | }; 19 | 20 | return PyModule_Create(&moduledef); 21 | } 22 | 23 | #include "pyoppaimodule.cc" 24 | 25 | /* 26 | // example embedded main: 27 | int main(int argc, char* argv[]) 28 | { 29 | wchar_t* progname = python_decode_locale(argv[0], 0); 30 | if (!progname) { 31 | fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); 32 | exit(1); 33 | } 34 | 35 | PyImport_AppendInittab(PYOPPAI_NAME, PYTHON_MODINIT_FUNC_NAME(pyoppai)); 36 | Py_SetProgramName(progname); 37 | Py_Initialize(); 38 | 39 | // do shit 40 | 41 | PyMem_RawFree(progname); // probably not needed since we are exiting 42 | 43 | return 0; 44 | } 45 | */ 46 | -------------------------------------------------------------------------------- /pyoppai/setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from distutils.core import setup, Extension 3 | 4 | # TODO: should i split this into one setup.py per platform? this feels messy 5 | 6 | pyoppai = Extension( 7 | 'pyoppai', 8 | 9 | define_macros = [ 10 | ('OPPAI_LIB', '1'), 11 | ('OPPAI_SSIZE_T_DEFINED', '1'), 12 | ('_CRT_SECURE_NO_WARNINGS', '1'), 13 | ('NOMINMAX', '1') 14 | ] if "win" in sys.platform else [ 15 | ('OPPAI_LIB', '1'), 16 | #('OPPAI_MODULE_DEBUG', '1'), 17 | ], 18 | 19 | extra_compile_args = [ 20 | "-O2", 21 | "-nologo", "-MT", "-Gm-", "GR-", "-EHsc", "-W4", #"-WX", 22 | "-wd4201", "-wd4100", "-F8000000" 23 | ] if "win" in sys.platform else [ 24 | "-O2", 25 | "-Wno-variadic-macros", 26 | "-Wall", #"-Werror" #TODO: fix warnings and use -Werror? 27 | ], 28 | 29 | libraries = [ 30 | 'Advapi32' if "win" in sys.platform else 'crypto' 31 | ], 32 | 33 | sources = [ 34 | 'python27_pyoppaimodule.cc' if sys.version_info[0] < 3 else 35 | 'python3_pyoppaimodule.cc' 36 | ] 37 | ) 38 | 39 | setup( 40 | name = 'pyoppai', 41 | version = '0.9.9-b1.2', 42 | description = 'Python bindings for the oppai osu! pp calculator', 43 | ext_modules = [pyoppai] 44 | ) 45 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname $0) 4 | 5 | git pull origin master 6 | 7 | echo -e "\nCompiling and Stripping" 8 | . "$dir"/build.sh -static -no-pie || exit 1 9 | 10 | echo -e "\nPackaging" 11 | folder="oppai-$(./oppai -version)-" 12 | folder="${folder}$(uname | tr '[:upper:]' '[:lower:]')-$(uname -m)" 13 | mkdir -p "$folder" 14 | mv ./oppai $folder/oppai 15 | cp ./LICENSE $folder/LICENSE 16 | 17 | rm "$folder".tar.xz 18 | tar -cvJf "$folder".tar.xz \ 19 | "$folder"/oppai \ 20 | "$folder"/LICENSE 21 | 22 | echo -e "\nResult:" 23 | tar tf "$folder".tar.xz 24 | 25 | readelf --dynamic "$folder"/oppai 26 | ldd "$folder"/oppai 27 | 28 | -------------------------------------------------------------------------------- /test.cc: -------------------------------------------------------------------------------- 1 | #define OPPAI_STDINT 1 2 | #define OPPAI_LIB 1 3 | #include "main.cc" 4 | #include "test_suite.cc" /* defines suite */ 5 | 6 | #define BUFSIZE 8000000 7 | #define ERROR_MARGIN 0.02 /* pp can be off by +- 2% */ 8 | /* 9 | margin is actually 10 | - 3x for < 100pp 11 | - 2x for 100-200pp 12 | - 1.5x for 200-300pp 13 | */ 14 | 15 | #include 16 | 17 | static size_t wrchunk(void* p, size_t cb, size_t nmemb, void* fd) { 18 | return fwrite(p, cb, nmemb, (FILE*)fd); 19 | } 20 | 21 | static void check_err(oppai_ctx* ctx) 22 | { 23 | if (oppai_err(ctx)) { 24 | fprintf(stderr, "%s\n", oppai_err(ctx)); 25 | exit(1); 26 | } 27 | } 28 | 29 | static void print_score(struct score* s) 30 | { 31 | char mods_str_buf[20]; 32 | char* mods_str = mods_str_buf; 33 | 34 | strcpy(mods_str, "nomod"); 35 | 36 | # define m(mod) \ 37 | if (s->mods & mods::mod) { \ 38 | mods_str += sprintf(mods_str, #mod); \ 39 | } \ 40 | 41 | m(hr) m(nc) m(ht) m(so) m(nf) m(ez) m(dt) m(fl) m(hd) 42 | # undef m 43 | 44 | fprintf( 45 | stderr, 46 | "%" fu32 " +%s " 47 | "%" fu16 "x " 48 | "%" fu16 "x300 " 49 | "%" fu16 "x100 " 50 | "%" fu16 "x50 " 51 | "%" fu16 "xmiss " 52 | "%g pp\n", 53 | s->id, mods_str_buf, 54 | s->max_combo, s->n300, s->n100, s->n50, s->nmiss, s->pp 55 | ); 56 | } 57 | 58 | int main(int argc, char* argv[]) 59 | { 60 | CURL* curl = 0; 61 | 62 | char* buf; 63 | char fname_buf[4096]; 64 | char url_buf[128]; 65 | char* fname = fname_buf; 66 | char* url = url_buf; 67 | 68 | uint32_t i; 69 | uint32_t n = (uint32_t)(sizeof(suite) / sizeof(suite[0])); 70 | 71 | buf = (char*)malloc(BUFSIZE); 72 | mkdir("test_suite"); 73 | fname += sprintf(fname, "test_suite/"); 74 | 75 | for (i = 0; i < n; ++i) 76 | { 77 | struct score* s = &suite[i]; 78 | double aim = 0, speed = 0; 79 | double margin; 80 | 81 | print_score(s); 82 | sprintf(fname, "%" fu32 ".osu", s->id); 83 | 84 | trycalc: 85 | oppai_ctx ctx; 86 | beatmap b(&ctx); 87 | beatmap::parse(fname_buf, b, buf, BUFSIZE, 1); 88 | 89 | if (oppai_err(&ctx)) 90 | { 91 | /* TODO: properly error check&log curl 92 | also pull this out into a function maybe */ 93 | 94 | CURLcode res; 95 | FILE* f; 96 | 97 | fprintf(stderr, "%s\n", oppai_err(&ctx)); 98 | ctx.last_err = 0; 99 | 100 | if (!curl) 101 | { 102 | fprintf(stderr, "initializing curl\n"); 103 | curl_global_init(CURL_GLOBAL_ALL); 104 | 105 | curl = curl_easy_init(); 106 | if (!curl) { 107 | fprintf(stderr, "curl_easy_init failed\n"); 108 | exit(1); 109 | } 110 | 111 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); 112 | curl_easy_setopt( 113 | curl, CURLOPT_WRITEFUNCTION, wrchunk 114 | ); 115 | 116 | url += sprintf(url, "http://osu.ppy.sh/osu/"); 117 | } 118 | 119 | sprintf(url, "%" fu32, s->id); 120 | curl_easy_setopt(curl, CURLOPT_URL, url_buf); 121 | 122 | fprintf(stderr, "downloading %s\n", url_buf); 123 | f = fopen(fname_buf, "wb"); 124 | if (!f) { 125 | perror("fopen"); 126 | exit(1); 127 | } 128 | 129 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); 130 | res = curl_easy_perform(curl); 131 | 132 | if (res != CURLE_OK) 133 | { 134 | fprintf(stderr, "curl_easy_perform failed"); 135 | fclose(f); 136 | unlink(fname_buf); 137 | goto trycalc; 138 | } 139 | 140 | fclose(f); 141 | goto trycalc; 142 | } 143 | 144 | b.apply_mods(s->mods); 145 | 146 | d_calc_ctx dctx(&ctx); 147 | d_calc(&dctx, b, &aim, &speed); 148 | check_err(&ctx); 149 | 150 | pp_calc_result res = 151 | pp_calc( 152 | &ctx, aim, speed, b, 153 | s->mods, s->max_combo, s->nmiss, 154 | s->n300, s->n100, s->n50 155 | ); 156 | 157 | check_err(&ctx); 158 | 159 | margin = s->pp * ERROR_MARGIN; 160 | if (s->pp < 100) { 161 | margin *= 3; 162 | } 163 | else if (s->pp < 200) { 164 | margin *= 2; 165 | } 166 | else if (s->pp < 300) { 167 | margin *= 1.5; 168 | } 169 | 170 | if (fabs(res.pp - s->pp) >= margin) 171 | { 172 | fprintf( 173 | stderr, 174 | "failed test: got %g pp, expected %g\n", 175 | res.pp, s->pp 176 | ); 177 | 178 | exit(1); 179 | } 180 | } 181 | 182 | return 0; 183 | } 184 | 185 | -------------------------------------------------------------------------------- /types.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef float f32; 4 | typedef double f64; 5 | 6 | #ifndef OPPAI_STDINT 7 | typedef unsigned long long int u64; 8 | typedef unsigned int u32; 9 | typedef unsigned short int u16; 10 | typedef unsigned char u8; 11 | 12 | typedef long long int i64; 13 | typedef int i32; 14 | typedef short int i16; 15 | typedef signed char i8; 16 | #else 17 | #include 18 | 19 | typedef int_least64_t i64; 20 | typedef int_least32_t i32; 21 | typedef int_least16_t i16; 22 | typedef int_least8_t i8; 23 | 24 | typedef uint_least64_t u64; 25 | typedef uint_least32_t u32; 26 | typedef uint_least16_t u16; 27 | typedef uint_least8_t u8; 28 | #endif 29 | 30 | #define fi32 "d" 31 | #define fi16 "hd" 32 | 33 | #define fu32 "u" 34 | #define fu16 "hu" 35 | 36 | #if defined(_WIN32) || defined(_WIN64) 37 | #include 38 | 39 | #if !defined(OPPAI_SSIZE_T_DEFINED) 40 | typedef SSIZE_T ssize_t; 41 | #endif 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /win/amd64-msvc2010.bat: -------------------------------------------------------------------------------- 1 | call C:\\"Program Files"\\"Microsoft Visual Studio 10.0"\\VC\\vcvarsall.bat x86_amd64 2 | call build.bat 3 | -------------------------------------------------------------------------------- /win/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | mkdir ..\build 4 | pushd ..\build 5 | del oppai.exe 6 | del oppai.obj 7 | cl -D_CRT_SECURE_NO_WARNINGS=1 ^ 8 | -DNOMINMAX=1 ^ 9 | -O2 ^ 10 | -nologo -MT -Gm- -GR- -EHsc -W4 -WX ^ 11 | -wd4201 ^ 12 | -wd4100 ^ 13 | -wd4458 ^ 14 | -F8000000 ^ 15 | ..\main.cc ^ 16 | -Feoppai.exe ^ 17 | Advapi32.lib 18 | popd 19 | -------------------------------------------------------------------------------- /win/i386-msvc2010.bat: -------------------------------------------------------------------------------- 1 | call C:\\"Program Files"\\"Microsoft Visual Studio 10.0"\\VC\\vcvarsall.bat x86 2 | call build.bat 3 | --------------------------------------------------------------------------------