Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork6
A collection of utilities for creating rhythm games in Unity, Unreal, Godot, SDL and MonoGame.
License
neogeek/rhythm-game-utilities
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Caution
This package is in early development and should not be used in production.
This library is a collection of utilities for creating rhythm games like Tap Tap Revenge, Guitar Hero, and Rock Band. It is meant to be used within any game engine that supports loading C++ libraries, such as Unity, Unreal, Godot, SDL and MonoGame.
Prototype game built using these utilities.
- 🎵 Parse
.chartand.midisong files - 🎼 Calculate position to render notes
- 💯 Calculate hit accuracy
- 🥁 Determine if the current time is on the beat
- 💫 And more!
- Starthis repo on GitHub for updates
- Follow me onBluesky
- Join theDiscord
- Follow me onGitHub
This library aims to offer support for multiple platforms through a single codebase. This is highly ambitious, so if you run into an issue with your platform of choice during development, please leave a detailed bug report with as much information as possible. Also, as this library is relatively new, mobile platforms will be fully supported after all other platforms are complete.
| Engine | Language | Platform | Version | Tested | Stable |
|---|---|---|---|---|---|
| Unity | C# | macOS | 6000.0.22f1 2022.3.50f1 2021.3.44f1 | ✅ | ❌ |
| Unity | C# | Windows | 6000.0.22f1 2022.3.50f1 2021.3.44f1 | ✅ | ❌ |
| Unreal | C++ | macOS | 5.4.4 | ✅ | ❌ |
| Unreal | C++ | Windows | 5.4.4 | ✅ | ❌ |
| Godot 4 | GDScript | macOS | 4.3 | ✅ | ❌ |
| Godot 4 | GDScript | Windows | 4.3 | ✅ | ❌ |
| Godot 4 | C# | macOS | 4.3 | ✅ | ❌ |
| Godot 4 | C# | Windows | 4.3 | ✅ | ❌ |
| SDL | C++ | macOS | 2.30.8 | ✅ | ❌ |
| SDL | C++ | Windows | 2.30.8 | - | - |
| MonoGame | C# | macOS | 3.8.2 | ✅ | ❌ |
| MonoGame | C# | Windows | 3.8.2 | ✅ | ❌ |
- Add package via git URL
https://github.com/neogeek/rhythm-game-utilities.git?path=/UnityPackage - Import the sample project (optional)
- Check the materials to make sure they work in the version of Unity and render pipeline you selected.
- Clone this repo locally (using either a tagged release or the main development branch).
- Add the include path to your
<project>.Build.csfile.PublicIncludePaths.AddRange(newstring[]{"D:/git/github/rhythm-game-utilities/include"});
Download and install the latest release fromhttps://github.com/rhythm-game-utilities/godot-plugin.
Install the nuget packagecom.neogeek.rhythm-game-utilities via the CLI or from within your IDE.
dotnet add package com.neogeek.rhythm-game-utilities --version 1.0.0-alpha.4
- Clone this repo locally (using either a tagged release or the main development branch).
- Add the include path to your project.
- VS Code:
.vscode/c_cpp_properties.json"includePath": ["${workspaceFolder}/**","${HOME}/git/github/rhythm-game-utilities/include/**"]
- VS Code:
- Add the include path to your build command.
g++g++ -std=c++17 -o build/output src/*.cpp -Isrc \ -I"${HOME}/git/github/rhythm-game-utilities/include/" \ -I/opt/homebrew/Cellar/sdl2/2.30.8/include/SDL2 -L/opt/homebrew/Cellar/sdl2/2.30.8/lib \ -lSDL2
- Add the include path to your CMAKE
CMakeLists.txtfile.include_directories($ENV{HOME}/git/github/rhythm-game-utilities/include/)
Install the nuget packagecom.neogeek.rhythm-game-utilities via the CLI or from within your IDE.
dotnet add package com.neogeek.rhythm-game-utilities --version 1.0.0-alpha.4
Languages:
C#
usingRhythmGameUtilities;varsamples=newfloat[_audioSource.clip.samples*_audioSource.clip.channels];_audioSource.clip.GetData(samples,0);varcolor=Color.red;vartransparentColor=newColor(0,0,0,0);varwaveform=Audio.ConvertSamplesToWaveform(samples,_texture2D.width,_texture2D.height);for(varx=0;x<waveform.Length;x+=1){for(vary=0;y<waveform[x].Length;y+=1){_texture2D.SetPixel(x,y,waveform[x][y]==1?color:transparentColor);}}_texture2D.Apply();
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varvalue=Common.InverseLerp(0,10,5);Console.WriteLine(value);// 0.5
#include<iostream>#include"RhythmGameUtilities/Common.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto value =InverseLerp(0,10,5); std::cout << value << std::endl;// 0.5return0;}
extendsNodefunc_ready()->void:varvalue=rhythm_game_utilities.inverse_lerp(0,10,5)print(value)# 0.5
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varvalue=Common.Lerp(0,10,0.5f);Console.WriteLine(value);// 5
#include<iostream>#include"RhythmGameUtilities/Common.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto value =Lerp(0,10,0.5f); std::cout << value << std::endl;// 5return0;}
extendsNodefunc_ready()->void:varvalue=rhythm_game_utilities.lerp(0,10,0.5)print(value)# 5
Read more about.chart files:https://github.com/TheNathannator/GuitarGame_ChartFormats/blob/main/doc/FileFormats/.chart/Core%20Infrastructure.md
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varsections=Parsers.ParseSectionsFromChart(contents);varlyrics=Parsers.ParseLyricsFromChartSection(sections[NamedSection.Events]);Console.WriteLine(lyrics.Count);// 12
#include<iostream>#include"RhythmGameUtilities/File.hpp"#include"RhythmGameUtilities/Parsers.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto content =ReadStringFromFile("./song.chart");auto sections =ParseSectionsFromChart(content.c_str());auto lyrics =ParseLyricsFromChartSection( sections.at(ToString(NamedSection::Events))); std::cout <<size(lyrics) << std::endl;// 12return0;}
extendsNodefunc_ready()->void:varfile=FileAccess.open("res://song.chart",FileAccess.READ)varcontent=file.get_as_text()varsections=rhythm_game_utilities.parse_sections_from_chart(content)varlyrics=rhythm_game_utilities.parse_lyrics_from_chart_section(sections["Events"])print(lyrics)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varsections=Parsers.ParseSectionsFromChart(contents);varmetaData=Parsers.ParseMetaDataFromChartSection(sections[NamedSection.Song]);Console.WriteLine(metaData["Name"]);// Example SongConsole.WriteLine(metaData["Resolution"]);// 192Console.WriteLine(metaData["MusicStream"]);// Example Song.ogg
#include<iostream>#include"RhythmGameUtilities/File.hpp"#include"RhythmGameUtilities/Parsers.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto content =ReadStringFromFile("./song.chart");auto sections =ParseSectionsFromChart(content.c_str());auto metaData =ParseMetaDataFromChartSection( sections.at(ToString(NamedSection::Song))); std::cout << metaData["Name"] << std::endl;// Example Song std::cout << metaData["Resolution"] << std::endl;// 192 std::cout << metaData["MusicStream"] << std::endl;// Example Song.oggreturn0;}
extendsNodefunc_ready()->void:varfile=FileAccess.open("res://song.chart",FileAccess.READ)varcontent=file.get_as_text()varsections=rhythm_game_utilities.parse_sections_from_chart(content)varmeta_data=rhythm_game_utilities.parse_meta_data_from_chart_section(sections["Song"])print(meta_data)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varsections=Parsers.ParseSectionsFromChart(contents);varnotes=Parsers.ParseNotesFromChartSection(sections[$"{Difficulty.Expert}Single"]);Console.WriteLine(notes.Length);// 8
#include<iostream>#include"RhythmGameUtilities/File.hpp"#include"RhythmGameUtilities/Parsers.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto content =ReadStringFromFile("./song.chart");auto sections =ParseSectionsFromChart(content.c_str());auto notes =ParseNotesFromChartSection( sections.at(ToString(Difficulty::Expert) +"Single"));for (auto ¬e : notes) {if (note.HandPosition >5) {continue; } std::cout << note.Position <<"" << note.HandPosition << std::endl; }return0;}
extendsNodefunc_ready()->void:varfile=FileAccess.open("res://song.chart",FileAccess.READ)varcontent=file.get_as_text()varsections=rhythm_game_utilities.parse_sections_from_chart(content)varnotes=rhythm_game_utilities.parse_notes_from_chart_section(sections["ExpertSingle"])print(notes)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varsections=Parsers.ParseSectionsFromChart(contents);Console.WriteLine(sections.Count);// 4
#include<iostream>#include"RhythmGameUtilities/File.hpp"#include"RhythmGameUtilities/Parsers.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto content =ReadStringFromFile("./song.chart");auto sections =ParseSectionsFromChart(content.c_str()); std::cout <<size(sections) << std::endl;// 4return0;}
extendsNodefunc_ready()->void:varfile=FileAccess.open("res://song.chart",FileAccess.READ)varcontent=file.get_as_text()varsections=rhythm_game_utilities.parse_sections_from_chart(content)print(sections)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varsections=Parsers.ParseSectionsFromChart(contents);vartempoChanges=Parsers.ParseTempoChangesFromChartSection(sections[NamedSection.SyncTrack]);Console.WriteLine(tempoChanges.Length);// 7
#include<iostream>#include"RhythmGameUtilities/File.hpp"#include"RhythmGameUtilities/Parsers.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto content =ReadStringFromFile("./song.chart");auto sections =ParseSectionsFromChart(content.c_str());auto tempoChanges =ParseTempoChangesFromChartSection( sections.at(ToString(NamedSection::SyncTrack))); std::cout <<size(tempoChanges) << std::endl;// 7return0;}
extendsNodefunc_ready()->void:varfile=FileAccess.open("res://song.chart",FileAccess.READ)varcontent=file.get_as_text()varsections=rhythm_game_utilities.parse_sections_from_chart(content)vartempo_changes=rhythm_game_utilities.parse_tempo_changes_from_chart_section(sections["SyncTrack"])print(tempo_changes)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varsections=Parsers.ParseSectionsFromChart(contents);vartimeSignatureChanges=Parsers.ParseTimeSignatureChangesFromChartSection(sections[NamedSection.SyncTrack]);Console.WriteLine(timeSignatureChanges.Length);// 4
#include<iostream>#include"RhythmGameUtilities/File.hpp"#include"RhythmGameUtilities/Parsers.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto content =ReadStringFromFile("./song.chart");auto sections =ParseSectionsFromChart(content.c_str());auto timeSignatureChanges =ParseTimeSignatureChangesFromChartSection( sections.at(ToString(NamedSection::SyncTrack))); std::cout <<size(timeSignatureChanges) << std::endl;// 4return0;}
extendsNodefunc_ready()->void:varfile=FileAccess.open("res://song.chart",FileAccess.READ)varcontent=file.get_as_text()varsections=rhythm_game_utilities.parse_sections_from_chart(content)vartime_signature_changes=rhythm_game_utilities.parse_time_signature_changes_from_chart_section(sections["SyncTrack"])print(time_signature_changes)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;constintseconds=2;constintresolution=192;constintpositionDelta=50;vartempoChanges=newTempo[]{new(){Position=0,BPM=120000}};vartimeSignatureChanges=newTimeSignature[]{new(){Position=0,Numerator=4,Denominator=2}};varnote=newNote{Position=750};varcurrentPosition=Utilities.ConvertSecondsToTicks(seconds,resolution,tempoChanges,timeSignatureChanges);varvalue=Utilities.CalculateAccuracyRatio(note.Position,currentPosition,positionDelta);Console.WriteLine(value);// 0.64
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){constint seconds =2;constint resolution =192;constint positionDelta =50; std::vector<Tempo> tempoChanges = {{0,120000}}; std::vector<TimeSignature> timeSignatureChanges = {{0,4}};auto note =new Note{750};auto currentPosition =ConvertSecondsToTicks( seconds, resolution, tempoChanges, timeSignatureChanges);auto value =CalculateAccuracyRatio(note->Position, currentPosition, positionDelta); std::cout << value << std::endl;// 0.64return0;}
extendsNodefunc_ready()->void:varseconds=2varresolution=192varposition_delta=50vartempo_changes= [{"position":0,"bpm":120000 }]vartime_signature_changes= [{"position":0,"numerator":4,"denominator":2 }]varcurrent_position=rhythm_game_utilities.convert_seconds_to_ticks(seconds,resolution,tempo_changes,time_signature_changes)varvalue=rhythm_game_utilities.calculate_accuracy_ratio(750,current_position,position_delta)print(round(value*100)/100.0)# 0.64
Languages:
C#C++GDScript
vartempoChanges=newTempo[]{new(){Position=0,BPM=88000},new(){Position=3840,BPM=112000},new(){Position=9984,BPM=89600},new(){Position=22272,BPM=112000},new(){Position=33792,BPM=111500},new(){Position=34560,BPM=112000},new(){Position=42240,BPM=111980}};varbeatBars=Utilities.CalculateBeatBars(tempoChanges);Console.WriteLine(beatBars.Length);// 440
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){constint resolution =192;constint timeSignature =4; std::vector<Tempo> tempoChanges = { {0,88000}, {3840,112000}, {9984,89600}, {22272,112000}, {33792,111500}, {34560,112000}, {42240,111980}};auto beatBars =CalculateBeatBars(tempoChanges, resolution, timeSignature,true); std::cout <<size(beatBars) << std::endl;// 440return0;}
extendsNodefunc_ready()->void:varresolution=192vartime_signature=4vartempo_changes= [{"position":0,"bpm":8800 },{"position":3840,"bpm":112000 },{"position":9984,"bpm":89600 },{"position":22272,"bpm":112000 },{"position":33792,"bpm":111500 },{"position":34560,"bpm":112000 },{"position":42240,"bpm":111980 }]varbeat_bars=rhythm_game_utilities.calculate_beat_bars(tempo_changes,resolution,time_signature,true)print(beat_bars)
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;constintseconds=5;constintresolution=192;vartempoChanges=newTempo[]{new(){Position=0,BPM=88000},new(){Position=3840,BPM=112000},new(){Position=9984,BPM=89600},new(){Position=22272,BPM=112000},new(){Position=33792,BPM=111500},new(){Position=34560,BPM=112000},new(){Position=42240,BPM=111980}};vartimeSignatureChanges=newTimeSignature[]{new(){Position=0,Numerator=4,Denominator=2}};varticks=Utilities.ConvertSecondsToTicks(seconds,resolution,tempoChanges,timeSignatureChanges);Console.WriteLine(ticks);// 1408
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){constint seconds =5;constint resolution =192; std::vector<Tempo> tempoChanges = { {0,88000}, {3840,112000}, {9984,89600}, {22272,112000}, {33792,111500}, {34560,112000}, {42240,111980}}; std::vector<TimeSignature> timeSignatureChanges = {{0,4,2}};auto ticks =ConvertSecondsToTicks(seconds, resolution, tempoChanges, timeSignatureChanges); std::cout << ticks << std::endl;// 1408return0;}
extendsNodefunc_ready()->void:varseconds=5varresolution=192vartempo_changes= [{"position":0,"bpm":88000 },{"position":3840,"bpm":112000 },{"position":9984,"bpm":89600 },{"position":22272,"bpm":112000 },{"position":33792,"bpm":111500 },{"position":34560,"bpm":112000 },{"position":42240,"bpm":111980 }]vartime_signature_changes= [{"position":0,"numerator":4,"denominator":2 }]varcurrent_position=rhythm_game_utilities.convert_seconds_to_ticks(seconds,resolution,tempo_changes,time_signature_changes)print(current_position)# 1408
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;constinttick=1056;constintresolution=192;varposition=Utilities.ConvertTickToPosition(tick,resolution);Console.WriteLine(position);// 5.5
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){constint tick =1056;constint resolution =192;auto position =ConvertTickToPosition(tick, resolution); std::cout << position << std::endl;// 5.5return0;}
extendsNodefunc_ready()->void:vartick=1056varresolution=192varposition=rhythm_game_utilities.convert_tick_to_position(tick,resolution)print(position)# 5.5
Languages:
C#C++GDScript
varnotes=newNote[]{new(){Position=768},new(){Position=960},new(){Position=1152},new(){Position=1536},new(){Position=1728},new(){Position=1920},new(){Position=2304},new(){Position=2496},new(){Position=2688},new(){Position=3072},new(){Position=3264}};varnote=Utilities.FindPositionNearGivenTick(notes,750);if(note!=null){Console.Write(note.Value.Position);// 768}
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){ std::vector<Note> notes = {{768,0,0}, {960,0,0}, {1152,0,0}, {1536,0,0}, {1728,0,0}, {1920,0,0}, {2304,0,0}, {2496,0,0}, {2688,0,0}, {3072,0,0}, {3264,0,0}};auto note =FindPositionNearGivenTick(notes,750);if (note) { std::cout << note->Position << std::endl;// 768 }return0;}
extendsNodefunc_ready()->void:varseconds=5varresolution=192vardelta=50varnotes= [{"position":768 }, {"position":960 }, {"position":1152 },{"position":1536 }, {"position":1728 }, {"position":1920 },{"position":2304 }, {"position":2496 }, {"position":2688 },{"position":3072 }, {"position":3264 }]varnote=rhythm_game_utilities.find_position_near_given_tick(notes,750,delta);print(note["position"])# 768
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;constintbpm=120;constfloatcurrentTime=10f;constfloatdelta=0.05f;varisOnTheBeat=Utilities.IsOnTheBeat(bpm,currentTime,delta);Console.WriteLine(isOnTheBeat?"Is on the beat!":"Is not on the beat!");// "Is on the beat!"
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){constint bpm =120;constfloat currentTime =10;constfloat delta =0.05f;auto isOnTheBeat =IsOnTheBeat(bpm, currentTime, delta); std::cout << (isOnTheBeat ?"Is on the beat!" :"Is not on the beat!") << std::endl;// "Is on the beat!"return0;}
extendsNodefunc_ready()->void:varbpm=120varcurrent_time=10vardelta=0.05varisOnTheBeat=rhythm_game_utilities.is_on_the_beat(bpm,current_time,delta)ifisOnTheBeat:# "Is on the beat!"print("Is on the beat!")else:print("Is not on the beat!")
Languages:
C#C++GDScript
usingSystem;usingRhythmGameUtilities;varvalue=Utilities.RoundUpToTheNearestMultiplier(12,10);Console.WriteLine(value);// 20
#include<iostream>#include"RhythmGameUtilities/Utilities.hpp"usingnamespaceRhythmGameUtilities;intmain(){auto value =RoundUpToTheNearestMultiplier(12,10); std::cout << value << std::endl;// 20return0;}
extendsNodefunc_ready()->void:varvalue=rhythm_game_utilities.round_up_to_the_nearest_multiplier(12,10)print(value)# 20
The current architecture for this project looks like this:
graph LR; file[/"song.chart"/] subgraph audioGraph ["Audio"] convertSamplesToWaveform["ConvertSamplesToWaveform()"] end subgraph commonGraph ["Common"] inverseLerp["InverseLerp()"] lerp["Lerp()"] end subgraph parsersGraph ["Parsers"] parseSectionsFromChart["ParseSectionsFromChart()"] parseLyricsFromChartSection["ParseLyricsFromChartSection()"] parseMetaDataFromChartSection["ParseMetaDataFromChartSection()"] parseNotesFromChartSection["ParseNotesFromChartSection()"] parseTempoChangesFromChartSection["ParseTempoChangesFromChartSection()"] parseTimeSignaturesChangesFromChartSection["ParseTimeSignatureChangesFromChartSection()"] parseSectionsFromChart-->parseLyricsFromChartSection parseSectionsFromChart-->parseMetaDataFromChartSection parseSectionsFromChart-->parseNotesFromChartSection parseSectionsFromChart-->parseTempoChangesFromChartSection parseSectionsFromChart-->parseTimeSignaturesChangesFromChartSection end subgraph utilitiesGraph ["Utilities"] calculateAccuracyRatio["CalculateAccuracyRatio()"] calculateBeatBars["CalculateBeatBars()"] convertSecondsToTicks["ConvertSecondsToTicks()"] convertTickToPosition["ConvertTickToPosition()"] isOnTheBeat["IsOnTheBeat()"] roundUpToTheNearestMultiplier["RoundUpToTheNearestMultiplier()"] end file-->parseSectionsFromChart convertSecondsToTicks-->calculateAccuracyRatio parseMetaDataFromChartSection-->calculateAccuracyRatio parseNotesFromChartSection-->calculateAccuracyRatio parseMetaDataFromChartSection-->calculateBeatBars parseTempoChangesFromChartSection-->calculateBeatBars parseMetaDataFromChartSection-->convertSecondsToTicks parseTempoChangesFromChartSection-->convertSecondsToTicks parseTimeSignaturesChangesFromChartSection-->convertSecondsToTicks parseMetaDataFromChartSection-->convertTickToPosition parseMetaDataFromChartSection-->isOnTheBeatThe Unity plugin includes compiled C++ libraries (macOS, Windows and Linux) and wraps the internal calls in native C# functions. These functions pass and retrieve the data from the C++ library and clean up memory upon completion.
There isn't a custom wrapper or plugin for Unreal, as the C++ library works as is when included as a header-only library.
Coming soon.
There isn't a custom wrapper or plugin for SDL, as the C++ library works as is when included as a header-only library.
The git hooks that run are quick file checks to ensure the files in the dotnet project and the UnityProject are the same and that the build files haven't changed.
$ git config --local core.hooksPath .githooks/
Run all tests viamake test.
- Tests for the C++ library are authored using the C++ native library
cassert. - Tests are run automatically via GitHub Actions on each new PR.
- For you add a new feature or fix a bug, please include the benchmark output in the PR along with your device stats.
If you want to test the project from within Unity, add the test namespace to your project by adding the following to yourPackages/manifest.json file:
{..."testables": ["com.scottdoxey.rhythm-game-utilities"]...}Warning
Do not commit any build changes to the repo. The build files are automatically generated via GitHub Actions.
When developing on macOS, make sure thatMac is selected in the bottom right-hand corner of Visual Studio Code or C++ Intellisense will not work.
./bin/build.sh
When developing on Windows, make sure thatWin32 is selected in the bottom right-hand corner of Visual Studio Code or C++ Intellisense will not work.
Run fromx64 Native Tools Command Prompt for VS:
call"./bin/build.bat"
Be sure to review theContributing Guidelines before logging an issue or making a pull request.
This project aims to help you build your rhythm game as fast as possible without needing to learn the complexities of a new library. Instead, you can utilize comprehensive examples and simple code recipes If you have feature requests or bugs, please create an issue and tag them with the appropriate tag. If an issue already exists, vote for it with 👍.
| Name | Description | Link |
|---|---|---|
| tiny-midi | Tiny wrapper around Window/macOS native MIDI libraries for reading MIDI input. | https://github.com/neogeek/tiny-midi |
| chart-to-json | Parse .chart files in JavaScript or the command line. | https://github.com/neogeek/chart-to-json |
About
A collection of utilities for creating rhythm games in Unity, Unreal, Godot, SDL and MonoGame.
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.