diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ea7c7d78 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + # Look for GitHub Actions workflows in the `root` directory + directory: "/" + # Check the for updates once a week + schedule: + interval: "weekly" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1d290375..2881c334 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,45 +1,40 @@ +name: Build & Latest Beta + on: push: pull_request: - branches: - - beta paths-ignore: - '**.md' -name: Latest Beta - jobs: windows: name: "Build Windows x64" runs-on: windows-2022 - defaults: - run: - working-directory: ${{github.workspace}}/main + permissions: + contents: read steps: - name: Get AV uses: actions/checkout@v3 - with: - path: main - name: Get MSBuild uses: microsoft/setup-msbuild@v2 - name: Obtain oggenc2.exe run: > curl https://www.rarewares.org/files/ogg/oggenc2.88-1.3.7-x64.zip --output oggenc.zip && unzip oggenc.zip -d . + shell: bash - name: Build AV run: msbuild build\VisualStudio\ArrowVortex.vcxproj /p:Configuration=Release /p:Platform=x64 - name: Collect into a directory + if: github.ref_name == 'beta' run: | mkdir AV - cd AV - cp -r ../bin/assets . - cp -r ../bin/noteskins . - cp -r ../bin/settings . - cp ../bin/ArrowVortex.exe . - cp ../oggenc2.exe . + cp -r bin/{assets,noteskins,settings} AV + cp bin/ArrowVortex.exe oggenc2.exe AV + shell: bash - name: Upload artifact + if: github.ref_name == 'beta' uses: actions/upload-artifact@v4 with: name: AV - path: main/AV/ + path: AV/ if-no-files-found: error diff --git a/src/Core/Utils.h b/src/Core/Utils.h index da7994a5..f9e32205 100644 --- a/src/Core/Utils.h +++ b/src/Core/Utils.h @@ -326,6 +326,25 @@ inline color32 ToColor32(float r, float g, float b, float a) return u32; } +static int gcd(int a, int b) +{ + if (a == 0) + { + return b; + } + if (b == 0) + { + return a; + } + if (a > b) + { + return gcd(a - b, b); + } + else + { + return gcd(a, b - a); + } +} }; // namespace Vortex #undef TT \ No newline at end of file diff --git a/src/Dialogs/AdjustSync.cpp b/src/Dialogs/AdjustSync.cpp index 154df092..a3900d78 100644 --- a/src/Dialogs/AdjustSync.cpp +++ b/src/Dialogs/AdjustSync.cpp @@ -50,7 +50,7 @@ WgSpinner* DialogAdjustSync::myCreateWidgetRow(StringRef label, double& val, int { WgSpinner* spinner = myLayout.add(label); spinner->value.bind(&val); - spinner->setPrecision(3, 3); + spinner->setPrecision(3, 6); spinner->onChange.bind(this, &DialogAdjustSync::onAction, setAction); spinner->setTooltip(tooltip1); diff --git a/src/Dialogs/AdjustTempo.cpp b/src/Dialogs/AdjustTempo.cpp index a7423a1c..5331a4be 100644 --- a/src/Dialogs/AdjustTempo.cpp +++ b/src/Dialogs/AdjustTempo.cpp @@ -91,10 +91,12 @@ void DialogAdjustTempo::myCreateWidgets() WgSpinner* bpm = myCreateWidgetRow("BPM", 0, myBPM, ACT_BPM_SET); bpm->setRange(VC_MIN_BPM, VC_MAX_BPM); + bpm->setPrecision(3, 6); bpm->setStep(1.0); WgSpinner* stop = myCreateWidgetRow("Stop", 28, myStop, ACT_STOP_SET); stop->setRange(VC_MIN_STOP, VC_MAX_STOP); + stop->setPrecision(3, 6); stop->setStep(0.001); myLayout.row().col(242); diff --git a/src/Dialogs/AdjustTempoSM5.cpp b/src/Dialogs/AdjustTempoSM5.cpp index c3c65293..3b6c9f1d 100644 --- a/src/Dialogs/AdjustTempoSM5.cpp +++ b/src/Dialogs/AdjustTempoSM5.cpp @@ -61,7 +61,7 @@ void DialogAdjustTempoSM5::myCreateWidgets() WgSpinner* spinner = myLayout.add("Delay"); spinner->value.bind(&myDelay); - spinner->setPrecision(3, 3); + spinner->setPrecision(3, 6); spinner->setStep(0.001); spinner->setRange(0, 1000); spinner->onChange.bind(this, &DialogAdjustTempoSM5::onAction, (int)ACT_DELAY_SET); @@ -70,7 +70,7 @@ void DialogAdjustTempoSM5::myCreateWidgets() myLayout.row().col(84).col(154); spinner = myLayout.add("Warp"); spinner->value.bind(&myWarp); - spinner->setPrecision(3, 3); + spinner->setPrecision(3, 6); spinner->setRange(0, 1000); spinner->onChange.bind(this, &DialogAdjustTempoSM5::onAction, (int)ACT_WARP_SET); spinner->setTooltip("Warp length at the current beat, in beats"); @@ -120,7 +120,7 @@ void DialogAdjustTempoSM5::myCreateWidgets() spinner = myLayout.add("Speed"); spinner->value.bind(&mySpeedRatio); - spinner->setPrecision(2, 2); + spinner->setPrecision(2, 6); spinner->setStep(0.1); spinner->setRange(0, 1000); spinner->onChange.bind(this, &DialogAdjustTempoSM5::onAction, (int)ACT_SPEED_SET); @@ -128,7 +128,7 @@ void DialogAdjustTempoSM5::myCreateWidgets() spinner = myLayout.add(); spinner->value.bind(&mySpeedDelay); - spinner->setPrecision(2, 2); + spinner->setPrecision(2, 6); spinner->setStep(0.1); spinner->setRange(0, 1000); spinner->onChange.bind(this, &DialogAdjustTempoSM5::onAction, (int)ACT_SPEED_SET); @@ -145,7 +145,7 @@ void DialogAdjustTempoSM5::myCreateWidgets() spinner = myLayout.add("Scroll"); spinner->value.bind(&myScrollRatio); - spinner->setPrecision(2, 2); + spinner->setPrecision(2, 6); spinner->setStep(0.1); spinner->setRange(0, 1000); spinner->onChange.bind(this, &DialogAdjustTempoSM5::onAction, (int)ACT_SCROLL_SET); @@ -153,7 +153,7 @@ void DialogAdjustTempoSM5::myCreateWidgets() spinner = myLayout.add("Fakes"); spinner->value.bind(&myFakeBeats); - spinner->setPrecision(3, 3); + spinner->setPrecision(3, 6); spinner->setRange(0, 1000); spinner->onChange.bind(this, &DialogAdjustTempoSM5::onAction, (int)ACT_FAKE_SET); spinner->setTooltip("Fake region, in beats"); diff --git a/src/Dialogs/CustomSnap.cpp b/src/Dialogs/CustomSnap.cpp index e7808d89..18212c0d 100644 --- a/src/Dialogs/CustomSnap.cpp +++ b/src/Dialogs/CustomSnap.cpp @@ -26,17 +26,16 @@ namespace Vortex { WgSpinner* scol = myLayout.add("Snapping"); scol->value.bind(&myCustomSnap); scol->onChange.bind(this, &DialogCustomSnap::onChange); - scol->setRange(1.0, 192.0); + scol->setRange(4.0, 192.0); scol->setPrecision(0, 0); scol->startCapturingText(); } void DialogCustomSnap::onChange() { - if (myCustomSnap > 0 && myCustomSnap <= 192) + if (myCustomSnap >= 4 && myCustomSnap <= 192) { gView->setCustomSnap(myCustomSnap); - //requestClose(); } } }; // namespace Vortex \ No newline at end of file diff --git a/src/Editor/Editing.cpp b/src/Editor/Editing.cpp index b8ef1b08..878c5636 100644 --- a/src/Editor/Editing.cpp +++ b/src/Editor/Editing.cpp @@ -327,6 +327,26 @@ void onChanges(int changes) // ================================================================================================ // EditingImpl :: member functions. +static int gcd(int a, int b) +{ + if (a == 0) + { + return b; + } + if (b == 0) + { + return a; + } + if (a > b) + { + return gcd(a - b, b); + } + else + { + return gcd(a, b - a); + } +} + void finishNotePlacement(int col) { auto& pnote = myPlacingNotes[col]; @@ -348,6 +368,14 @@ void finishNotePlacement(int col) } } + if (note.quant > 0 && note.quant <= 192) + { + note.quant = min(192u, note.quant * gView->getSnapQuant() / gcd(note.quant, gView->getSnapQuant())); + } + else + { + note.quant = 192; + } NoteEdit edit; edit.add.append(note); gNotes->modify(edit, false); diff --git a/src/Editor/LoadMp3.cpp b/src/Editor/LoadMp3.cpp index 8cda7ae9..8298baba 100644 --- a/src/Editor/LoadMp3.cpp +++ b/src/Editor/LoadMp3.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -431,19 +432,18 @@ int MP3Loader::readFrames(int frames, short* buffer) SoundSource* LoadMP3(FileReader* file, String& title, String& artist) { - MP3Loader* loader = new MP3Loader; + std::unique_ptr loader = std::make_unique(); loader->file = file; // Decode and synth the first frame to check if the file is valid. if(!loader->decodeFirstFrame()) { loader->file = nullptr; - delete loader; return nullptr; } // The file is valid, return the MP3 loader. - return loader; + return loader.release(); } }; // namespace Vortex diff --git a/src/Editor/LoadWav.cpp b/src/Editor/LoadWav.cpp index 1d5722ea..dcba02f4 100644 --- a/src/Editor/LoadWav.cpp +++ b/src/Editor/LoadWav.cpp @@ -14,17 +14,17 @@ namespace { #pragma pack(1) struct WaveHeader { - uint8_t chunkId[4]; + uint8_t chunkId[4]; // "RIFF" uint32_t chunkSize; - uint8_t format[4]; - uint8_t subChunkId[4]; - uint32_t subChunkSize; + uint8_t format[4]; // "WAVE" + uint8_t subchunk1Id[4]; // "fmt " + uint32_t subchunk1Size; uint16_t audioFormat; uint16_t numChannels; uint32_t sampleRate; uint32_t byteRate; uint16_t blockAlign; - uint16_t bps; + uint16_t bitsPerSample; }; struct WaveData { @@ -72,24 +72,30 @@ SoundSource* LoadWav(FileReader* file, String& title, String& artist) if(file->read(&header, sizeof(WaveHeader), 1) == 0 || memcmp(header.chunkId, "RIFF", 4) != 0 || memcmp(header.format, "WAVE", 4) != 0 - || memcmp(header.subChunkId, "fmt ", 4) != 0 + || memcmp(header.subchunk1Id, "fmt ", 4) != 0 || header.audioFormat != 1 || header.sampleRate == 0 || header.numChannels == 0 - || (header.bps != 8 && header.bps != 16 && header.bps != 24)) + || (header.bitsPerSample != 8 && header.bitsPerSample != 16 && header.bitsPerSample != 24)) { return nullptr; } // Skip over additional parameters at the end of the format chunk. - file->skip(header.subChunkSize - 16); + if (header.subchunk1Size > 16) + { + size_t extraBytes = static_cast(header.subchunk1Size) - 16; + file->skip(extraBytes); + } // Read the start of the data chunk. WaveData data; - if(file->read(&data, sizeof(WaveData), 1) == 0 - || memcmp(data.chunkId, "data", 4) != 0) - { - return nullptr; + while (true) { + if (file->read(&data, sizeof(WaveData), 1) == 0) + return nullptr; + if (memcmp(data.chunkId, "data", 4) == 0) + break; + file->skip(data.chunkSize); } // Create a wav loader that will read the contents of the data chunk. @@ -97,7 +103,7 @@ SoundSource* LoadWav(FileReader* file, String& title, String& artist) loader->frequency = header.sampleRate; loader->numChannels = header.numChannels; - loader->bytesPerSample = header.bps / 8; + loader->bytesPerSample = header.bitsPerSample / 8; loader->numFrames = data.chunkSize / (loader->bytesPerSample * loader->numChannels); loader->numFramesLeft = loader->numFrames; loader->file = file; diff --git a/src/Editor/TextOverlay.cpp b/src/Editor/TextOverlay.cpp index 651b16cf..f98d816f 100644 --- a/src/Editor/TextOverlay.cpp +++ b/src/Editor/TextOverlay.cpp @@ -591,7 +591,7 @@ void drawAbout() { vec2i size = gSystem->getWindowSize(); - Text::arrange(Text::BC, "ArrowVortex release v1.0.0"); + Text::arrange(Text::BC, "ArrowVortex release v1.0.1"); Text::draw(vec2i{size.x / 2, size.y / 2 - 128}); String buildDate = "Build date: " + System::getBuildData(); Text::arrange(Text::TC, buildDate.str()); @@ -618,6 +618,7 @@ void drawAbout() "@Psycast/Velocity\n" "@DeltaEpsilon7787/Delta Epsilon\n" "@DolpinChips/insep\n" + "@ScottBrenner/bren\n" "\n" "Original program and many thanks to : \n" "Bram 'Fietsemaker' van de Wetering\n"); diff --git a/src/Editor/View.cpp b/src/Editor/View.cpp index cd8085b8..4061ebd6 100644 --- a/src/Editor/View.cpp +++ b/src/Editor/View.cpp @@ -71,7 +71,7 @@ ViewImpl() , myZoomLevel(8) , myScaleLevel(4) , mySnapType(ST_NONE) - , myCustomSnap(1) + , myCustomSnap(20) , myUseTimeBasedView(true) , myUseReverseScroll(false) , myUseChartPreview(false) @@ -100,7 +100,7 @@ void loadSettings(XmrNode& settings) view->get("receptorX", &myReceptorX); view->get("receptorY", &myReceptorY); - myCustomSnap = min(max(myCustomSnap, 1), 192); + myCustomSnap = min(max(myCustomSnap, 5), 191); myZoomLevel = min(max(myZoomLevel, -2.0), 16.0); myScaleLevel = min(max(myScaleLevel, 1.0), 10.0); } @@ -441,7 +441,7 @@ void setSnapType(int type) void setCustomSnap(int size) { - if (size < 1) size = 1; + if (size < 4) size = 4; if (size > 192) size = 192; // If the custom snap is a non-custom value, set the snap to that value instead for (int i = 0; i < ST_CUSTOM; i++) diff --git a/src/Editor/Waveform.cpp b/src/Editor/Waveform.cpp index 92bf40ef..29217d94 100644 --- a/src/Editor/Waveform.cpp +++ b/src/Editor/Waveform.cpp @@ -457,6 +457,12 @@ void sampleEdges(WaveEdge* edges, int w, int h, int channel, int blockId, bool f return; } + // A crash can occur if another thread is loading the audio. Just do nothing if it is. + if (!music.isAllocated()) + { + return; + } + double sampleSkip = max(0.001, (samplesPerPixel / 200.0)); int wh = w / 2 - 1; diff --git a/src/Managers/MetadataMan.cpp b/src/Managers/MetadataMan.cpp index f429c7e9..fd87ca1f 100644 --- a/src/Managers/MetadataMan.cpp +++ b/src/Managers/MetadataMan.cpp @@ -300,7 +300,7 @@ String findBackgroundFile() String findCdTitleFile() { - return findImageFile("cd", "cdtitle"); + return findImageFile("cd", "title"); } // ================================================================================================ diff --git a/src/Managers/TempoMan.cpp b/src/Managers/TempoMan.cpp index 74995443..0eb4b2bb 100644 --- a/src/Managers/TempoMan.cpp +++ b/src/Managers/TempoMan.cpp @@ -470,7 +470,7 @@ static bool Differs(const BpmRange& a, const BpmRange& b) static double ClampAndRound(double val, double min, double max) { - return round(clamp(val, min, max) * 1000.0) / 1000.0; + return round(clamp(val, min, max) * 1000000.0) / 1000000.0; } void modify(const SegmentEdit& edit) diff --git a/src/Simfile/LoadSm.cpp b/src/Simfile/LoadSm.cpp index b9a40f06..9b4a2dee 100644 --- a/src/Simfile/LoadSm.cpp +++ b/src/Simfile/LoadSm.cpp @@ -2,8 +2,10 @@ #include #include +#include #include +#include #include #include @@ -372,6 +374,7 @@ struct ReadNoteData NoteList* notes; Vector holdPos; Vector holdType; + Vector quants; }; static void ReadNoteRow(ReadNoteData& data, int row, char* p, int quantization) @@ -391,6 +394,7 @@ static void ReadNoteRow(ReadNoteData& data, int row, char* p, int quantization) data.notes->append({row, row, (uint)col, (uint)data.player, NOTE_STEP_OR_HOLD, (uint) quantization}); data.holdType[col] = (*p == '2') ? NOTE_STEP_OR_HOLD : NOTE_ROLL; data.holdPos[col] = data.notes->size(); + data.quants[col] = quantization; } else if(*p == '3') { @@ -400,7 +404,18 @@ static void ReadNoteRow(ReadNoteData& data, int row, char* p, int quantization) auto* hold = data.notes->begin() + holdPos - 1; hold->endrow = row; hold->type = data.holdType[col]; + // Make sure we set the note to its largest quantization to avoid data loss + if (data.quants[col] > 0 && hold->quant > 0) + { + hold->quant = min(192u, quantization * hold->quant / gcd(quantization, hold->quant)); + } + else // There was some error, so always play safe and use 192 + { + HudError("Bug: couldn't get hold quantization in row %d", row); + hold->quant = 192; + } data.holdPos[col] = 0; + data.quants[col] = 0; } } else if(*p == 'M') @@ -448,6 +463,7 @@ static void ParseNotes(ParseData& data, Chart* chart, StringRef style, char* not readNoteData.numCols = numCols; readNoteData.notes = &chart->notes; readNoteData.holdPos.resize(numCols, 0); + readNoteData.quants.resize(numCols, 0); readNoteData.holdType.resize(numCols, NOTE_STEP_OR_HOLD); int numSections = 0; diff --git a/src/Simfile/SaveSm.cpp b/src/Simfile/SaveSm.cpp index 0b179962..491f473f 100644 --- a/src/Simfile/SaveSm.cpp +++ b/src/Simfile/SaveSm.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -80,7 +81,7 @@ static void WriteTag(ExportData& data, const char* tag, double value, ForceWrite { if(ShouldWrite(data, when, value != 0, sscOnly)) { - data.file.printf("#%s:%.3f;\n", tag, value); + data.file.printf("#%s:%.6f;\n", tag, value); } } @@ -181,7 +182,7 @@ static void WriteBpms(ExportData& data, const Tempo* tempo) WriteSegments(data, "BPMS", tempo, START_OF_LINE, ',', SONG_ONLY, false, [&](const BpmChange& change) { - data.file.printf("%.3f=%.6f", + data.file.printf("%.6f=%.6f", ToBeat(change.row), change.bpm); }); } @@ -191,7 +192,7 @@ static void WriteStops(ExportData& data, const Tempo* tempo) WriteSegments(data, "STOPS", tempo, START_OF_LINE, ',', SONG_ONLY, false, [&](const Stop& stop) { - data.file.printf("%.3f=%.3f", + data.file.printf("%.6f=%.6f", ToBeat(stop.row), stop.seconds); }); } @@ -201,7 +202,7 @@ static void WriteDelays(ExportData& data, const Tempo* tempo) WriteSegments(data, "DELAYS", tempo, END_OF_LINE, ',', NEVER, true, [&](const Delay& delay) { - data.file.printf("%.3f=%.3f", + data.file.printf("%.6f=%.6f", ToBeat(delay.row), delay.seconds); }); } @@ -211,7 +212,7 @@ static void WriteWarps(ExportData& data, const Tempo* tempo) WriteSegments(data, "WARPS", tempo, END_OF_LINE, ',', NEVER, true, [&](const Warp& warp) { - data.file.printf("%.3f=%.3f", + data.file.printf("%.6f=%.6f", ToBeat(warp.row), ToBeat(warp.numRows)); }); } @@ -221,7 +222,7 @@ static void WriteSpeeds(ExportData& data, const Tempo* tempo) WriteSegments(data, "SPEEDS", tempo, END_OF_LINE, ',', NEVER, true, [&](const Speed& speed) { - data.file.printf("%.3f=%.3f=%.3f=%i", + data.file.printf("%.6f=%.6f=%.6f=%i", ToBeat(speed.row), speed.ratio, speed.delay, speed.unit); }); } @@ -231,7 +232,7 @@ static void WriteScrolls(ExportData& data, const Tempo* tempo) WriteSegments(data, "SCROLLS", tempo, END_OF_LINE, ',', NEVER, true, [&](const Scroll& scroll) { - data.file.printf("%.3f=%.3f", + data.file.printf("%.6f=%.6f", ToBeat(scroll.row), scroll.ratio); }); } @@ -241,7 +242,7 @@ static void WriteTickCounts(ExportData& data, const Tempo* tempo) WriteSegments(data, "TICKCOUNTS", tempo, END_OF_LINE, ',', NEVER, true, [&](const TickCount& tick) { - data.file.printf("%.3f=%i", + data.file.printf("%.6f=%i", ToBeat(tick.row), tick.ticks); }); } @@ -251,7 +252,7 @@ static void WriteTimeSignatures(ExportData& data, const Tempo* tempo) WriteSegments(data, "TIMESIGNATURES", tempo, END_OF_LINE, ',', NEVER, true, [&](const TimeSignature& sig) { - data.file.printf("%.3f=%i=%i", + data.file.printf("%.6f=%i=%i", ToBeat(sig.row), (int)ToBeat(sig.rowsPerMeasure), sig.beatNote); }); } @@ -261,7 +262,7 @@ static void WriteLabels(ExportData& data, const Tempo* tempo) WriteSegments