diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..03b00c7a960f50ef3ec8c7f236f4be3f458dbb9c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +**/.DS_Store +**/__pycache__ +node_modules +**/.vscode \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..b3837e7fb71fd253162c2467a358e7f2811293c4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,42 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +ExtraSounds/KneeSocks.m4a filter=lfs diff=lfs merge=lfs -text +ExtraSounds/lofiBeats.m4a filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..03b00c7a960f50ef3ec8c7f236f4be3f458dbb9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/.DS_Store +**/__pycache__ +node_modules +**/.vscode \ No newline at end of file diff --git a/Audios/AriaMath/original.m4a b/Audios/AriaMath/original.m4a new file mode 100644 index 0000000000000000000000000000000000000000..433fde7aceab15af151b2fde24df9cfe0d673934 Binary files /dev/null and b/Audios/AriaMath/original.m4a differ diff --git a/Audios/AriaMath/processed-mean.mp3 b/Audios/AriaMath/processed-mean.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cf113fcb99ab72685b2faf22771c3a5d3ff5441a --- /dev/null +++ b/Audios/AriaMath/processed-mean.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1163b83102bbaf5b0fe226548f768ab103f671d66bf21ee1fa6ce02c02e1299 +size 154481 diff --git a/Audios/AriaMath/result-mean.pkl b/Audios/AriaMath/result-mean.pkl new file mode 100644 index 0000000000000000000000000000000000000000..fd2ab8bc86ba8a12456c598c00bf3ac993c51227 --- /dev/null +++ b/Audios/AriaMath/result-mean.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faa2b572d2ac8801d41225d83aa41b81859ccfc551704d22fa16ff07b191487e +size 27759 diff --git a/Audios/DilKyuYeMera/original.mp3 b/Audios/DilKyuYeMera/original.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8d13acac41d1ae25fcf4ec2520ecac369a3c72a1 --- /dev/null +++ b/Audios/DilKyuYeMera/original.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2adb64e9c774b80b7ef3fdbb93fc9b58cfc64ba02d743c4423ff6363417b412 +size 234237 diff --git a/Audios/DilKyuYeMera/processed-mean.mp3 b/Audios/DilKyuYeMera/processed-mean.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..65ef8dcf834b44f3ced9c71bb5607c8abd55986c --- /dev/null +++ b/Audios/DilKyuYeMera/processed-mean.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0154c7575654564e583797e64ead00f74c91d06ef7bafce169929d579c0f2229 +size 129733 diff --git a/Audios/DilKyuYeMera/result-mean.pkl b/Audios/DilKyuYeMera/result-mean.pkl new file mode 100644 index 0000000000000000000000000000000000000000..30cd87300e28c467ff70c2875800f6bebab20b12 --- /dev/null +++ b/Audios/DilKyuYeMera/result-mean.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94ad2e21eca5590be3b0a9af0c380dc72376fab3ea1d96f4dbf66d8b4287e752 +size 24633 diff --git a/Audios/GhibliBeats/original.m4a b/Audios/GhibliBeats/original.m4a new file mode 100644 index 0000000000000000000000000000000000000000..3b5df86bfe476cec3b8255f0910809c0bc8c1b39 Binary files /dev/null and b/Audios/GhibliBeats/original.m4a differ diff --git a/Audios/GhibliBeats/processed-Max.mp3 b/Audios/GhibliBeats/processed-Max.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..042ca9bfc32f4aa3c369e9380c07ccbb69972ce5 --- /dev/null +++ b/Audios/GhibliBeats/processed-Max.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6f54c2b42e67072f7a50916502a1929d8dff7d53600b01165c0c72f3c57fd7e +size 74559 diff --git a/Audios/GhibliBeats/processed-Mean.mp3 b/Audios/GhibliBeats/processed-Mean.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5f18b0b21744abdfa91ae1180c194871409b194d --- /dev/null +++ b/Audios/GhibliBeats/processed-Mean.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:797aa050347e06103aade3af6afbb675284f9cf98bb692b43895d4aee69e0615 +size 83223 diff --git a/Audios/GhibliBeats/result-Max.pkl b/Audios/GhibliBeats/result-Max.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9c8898fc3ba56b4f1b811800451824ccb77da0f4 --- /dev/null +++ b/Audios/GhibliBeats/result-Max.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:646a6c438eea5262f0b73d5670791caf43e8f3d7fe9fbc354089a601f1dee8e6 +size 12413 diff --git a/Audios/GhibliBeats/result-Mean.pkl b/Audios/GhibliBeats/result-Mean.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b6e1ce090662fc971f7f68d88bea961362b4bd0b --- /dev/null +++ b/Audios/GhibliBeats/result-Mean.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef61cbec7e130cae0538a8cf644222893256190d08bd4027c488c47896990649 +size 15053 diff --git a/Audios/PiratesOfCarribean/original.mp3 b/Audios/PiratesOfCarribean/original.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d876126d778755513da3f1bae87c86941d56fb6e --- /dev/null +++ b/Audios/PiratesOfCarribean/original.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c3d6a50549237218c7be886144fc8aaf32f0ae5366b1cc5d4f6743fd899e707 +size 558189 diff --git a/Audios/PiratesOfCarribean/processed-mean.mp3 b/Audios/PiratesOfCarribean/processed-mean.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4fe5aa15b5431074207dbfca03611bae08f64d27 --- /dev/null +++ b/Audios/PiratesOfCarribean/processed-mean.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a9ebb7674e386867cca4071138bb5b77e2f0e6a3cc3f986b45792e0a4511c64 +size 205754 diff --git a/Audios/PiratesOfCarribean/result-mean.pkl b/Audios/PiratesOfCarribean/result-mean.pkl new file mode 100644 index 0000000000000000000000000000000000000000..6dc55d33e53222aa0a0585f1315b4329438cc197 --- /dev/null +++ b/Audios/PiratesOfCarribean/result-mean.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b35300d3f6bab53b2670b1af0ec9dae19071604ca04b4e1c81c0c409051c385c +size 41112 diff --git a/Audios/RiverFlowsInYou/original.m4a b/Audios/RiverFlowsInYou/original.m4a new file mode 100644 index 0000000000000000000000000000000000000000..23b97993359e1d813b7fd02b8ba5fce44894dfb2 Binary files /dev/null and b/Audios/RiverFlowsInYou/original.m4a differ diff --git a/Audios/RiverFlowsInYou/processed-Mean.mp3 b/Audios/RiverFlowsInYou/processed-Mean.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..34bf4e17c5857a01b1dce7f3209d389d04e8265b --- /dev/null +++ b/Audios/RiverFlowsInYou/processed-Mean.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca10be23fc408bb25923941be5d3d47ecf3247539eb263b2c7c7cdf88e7783f9 +size 98446 diff --git a/Audios/RiverFlowsInYou/result-Mean.pkl b/Audios/RiverFlowsInYou/result-Mean.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b7d8e02958b9cbd1721c44d04bb3138d54b0baa1 --- /dev/null +++ b/Audios/RiverFlowsInYou/result-Mean.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85b33ce601cfd0c56db9ae07c5ef2cd370801f6ffef0e22eb39991488a243c75 +size 19862 diff --git a/Audios/TetrisBeats/original.m4a b/Audios/TetrisBeats/original.m4a new file mode 100644 index 0000000000000000000000000000000000000000..e9fd5e3cd29d8a62ef299e6e61813cf7b1321416 Binary files /dev/null and b/Audios/TetrisBeats/original.m4a differ diff --git a/Audios/TetrisBeats/processed-Mean.mp3 b/Audios/TetrisBeats/processed-Mean.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..eda551977775663b4afae4ae1361e3346be9b70b --- /dev/null +++ b/Audios/TetrisBeats/processed-Mean.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6409eb6ee4e4dffa7640145983b8b255e1be351156442a7fe752b7a373187ab7 +size 58584 diff --git a/Audios/TetrisBeats/result-Mean.pkl b/Audios/TetrisBeats/result-Mean.pkl new file mode 100644 index 0000000000000000000000000000000000000000..f9d56085e3471cba4ddbcdb378e6e2cbf33915c3 --- /dev/null +++ b/Audios/TetrisBeats/result-Mean.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f69d540e66a101eefd64ffeed2f03d4f9bc05e19dcf2912bd23adade2cb8246 +size 13963 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..9ef876677c53085d924515e4660e87676121e27c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.12-slim +RUN useradd -m -u 1000 user +WORKDIR /minecraft-noteblock-music-generator +COPY --chown=user ./requirements.txt requirements.txt + +RUN apt-get update && \ + apt-get install -y ffmpeg && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir --upgrade -r requirements.txt +COPY --chown=user . . + +# RUN chown -R choreouser:choreo /usr/local/lib/python3.12/site-packages +# USER 10014 +# EXPOSE 8080 + +CMD ["python3", "app.py"] +# CMD ["gunicorn", "app:app", "-b", "0.0.0.0:7860"] \ No newline at end of file diff --git a/ExtraSounds/AsfAmplitudeCalculatingWithGap.m4a b/ExtraSounds/AsfAmplitudeCalculatingWithGap.m4a new file mode 100644 index 0000000000000000000000000000000000000000..8eada31b0f95df8b4443da09483a468a65d48e22 Binary files /dev/null and b/ExtraSounds/AsfAmplitudeCalculatingWithGap.m4a differ diff --git a/ExtraSounds/AsfAmplitudeCheckingWithoutGap.m4a b/ExtraSounds/AsfAmplitudeCheckingWithoutGap.m4a new file mode 100644 index 0000000000000000000000000000000000000000..f9a009b5e41c64bc033a642f74f4922caa153831 Binary files /dev/null and b/ExtraSounds/AsfAmplitudeCheckingWithoutGap.m4a differ diff --git a/ExtraSounds/InstrumentsBaseSoundsRecorded.m4a b/ExtraSounds/InstrumentsBaseSoundsRecorded.m4a new file mode 100644 index 0000000000000000000000000000000000000000..2824da5f978e6888863caa6822e7a2b76bd7c76c Binary files /dev/null and b/ExtraSounds/InstrumentsBaseSoundsRecorded.m4a differ diff --git a/ExtraSounds/KneeSocks.m4a b/ExtraSounds/KneeSocks.m4a new file mode 100644 index 0000000000000000000000000000000000000000..abbe192189c875389b7e73683f957d6e9d4c8955 --- /dev/null +++ b/ExtraSounds/KneeSocks.m4a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3441b724f6cbe51cc6ede2ed277b47113914f4d077842b1d1d1cc0c623f5f87d +size 1739305 diff --git a/ExtraSounds/gettysburg.wav b/ExtraSounds/gettysburg.wav new file mode 100644 index 0000000000000000000000000000000000000000..141c6a03285b887eba00edcbe52a2a27d63ea7cf --- /dev/null +++ b/ExtraSounds/gettysburg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7630daffb2f28f2724d81f1ff2039eb69a5fa360db3919721a77032a58db0d46 +size 775192 diff --git a/ExtraSounds/lofiBeats.m4a b/ExtraSounds/lofiBeats.m4a new file mode 100644 index 0000000000000000000000000000000000000000..0b79016e6006da38bdc47e88bbb8a9863c0a3e59 --- /dev/null +++ b/ExtraSounds/lofiBeats.m4a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18572b2b08aa8577cd48562e429dc4df5e0b25c03b5779d468be7ea5eeabc312 +size 1005582 diff --git a/MusicAnalyzer/Instruments/banjo.ogg b/MusicAnalyzer/Instruments/banjo.ogg new file mode 100644 index 0000000000000000000000000000000000000000..62ccb8c1408bb09b984db6cac960c07b122236c9 --- /dev/null +++ b/MusicAnalyzer/Instruments/banjo.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06b4e1927947bf2152d2d552099f753b2842f2419632f8effa1bad48d03af5f7 +size 12499 diff --git a/MusicAnalyzer/Instruments/bass.ogg b/MusicAnalyzer/Instruments/bass.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ea6a2e0aeae2b97d0252e78b9c1e1a3d051b8026 --- /dev/null +++ b/MusicAnalyzer/Instruments/bass.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7261568b63fea6a5763335479d16980329ae0c8db35f1b743eb86005d1a12150 +size 5574 diff --git a/MusicAnalyzer/Instruments/bdrum.ogg b/MusicAnalyzer/Instruments/bdrum.ogg new file mode 100644 index 0000000000000000000000000000000000000000..94c51b06099090b58e47907f8cf746b5e777bc67 --- /dev/null +++ b/MusicAnalyzer/Instruments/bdrum.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:829e1ff33546ef3de4e1e1a38ca3e0dc8a582227c284052868963333ed790686 +size 3850 diff --git a/MusicAnalyzer/Instruments/bell.ogg b/MusicAnalyzer/Instruments/bell.ogg new file mode 100644 index 0000000000000000000000000000000000000000..db6a27ad828a6fcb6026cf932f612c7ae6db8a70 --- /dev/null +++ b/MusicAnalyzer/Instruments/bell.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:550f0027fbaf52e1046898c05b65b9690d9db5374603fb701e5bd995f0d1059e +size 5827 diff --git a/MusicAnalyzer/Instruments/bit.ogg b/MusicAnalyzer/Instruments/bit.ogg new file mode 100644 index 0000000000000000000000000000000000000000..127cfe26eea95d94acdf3e428111e50bc172064f --- /dev/null +++ b/MusicAnalyzer/Instruments/bit.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f5948e882931aaaab6a2396e0fb77228929481150fc0b822e4ac0d4c6c3bcfb +size 11872 diff --git a/MusicAnalyzer/Instruments/chimes.ogg b/MusicAnalyzer/Instruments/chimes.ogg new file mode 100644 index 0000000000000000000000000000000000000000..bb011a0169c13c534a148c60b1f00d6e8be440e7 --- /dev/null +++ b/MusicAnalyzer/Instruments/chimes.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c8cc11b9f7e57d5645fd91cb2270b765067f3c01a064228b3ca6dc88bd188e7 +size 8606 diff --git a/MusicAnalyzer/Instruments/cow_bell.ogg b/MusicAnalyzer/Instruments/cow_bell.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d9cf110be1e99987aaf330af4fa206edcc1da0cd --- /dev/null +++ b/MusicAnalyzer/Instruments/cow_bell.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a18c945ff55914a4f51ddf6ecb012140cc737e63931947377c940e0d66474b7 +size 8428 diff --git a/MusicAnalyzer/Instruments/didgeridoo.ogg b/MusicAnalyzer/Instruments/didgeridoo.ogg new file mode 100644 index 0000000000000000000000000000000000000000..560c16963d10cc0a69ce7b087001d8e4c7a46c27 --- /dev/null +++ b/MusicAnalyzer/Instruments/didgeridoo.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7eaecdb39733fa5d1db91ec93fdc745a2cff9d0d77afdcbcfa9bc12568d542aa +size 12012 diff --git a/MusicAnalyzer/Instruments/flute.ogg b/MusicAnalyzer/Instruments/flute.ogg new file mode 100644 index 0000000000000000000000000000000000000000..718a4facb43f678ee6b74bfbaaefc81c6177680c --- /dev/null +++ b/MusicAnalyzer/Instruments/flute.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9372c07a7b9939d54ba877e25ba162806143b143bd05983f2bdc0a35c913fcbe +size 8781 diff --git a/MusicAnalyzer/Instruments/guitar.ogg b/MusicAnalyzer/Instruments/guitar.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3fb9c5eee40bbf2ce39146503fb1c8a34218d66f --- /dev/null +++ b/MusicAnalyzer/Instruments/guitar.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c15ed51b6fd1724e1fcc532d916961ca7925ccb0538cb7464eb84f747633469 +size 7563 diff --git a/MusicAnalyzer/Instruments/harp.ogg b/MusicAnalyzer/Instruments/harp.ogg new file mode 100644 index 0000000000000000000000000000000000000000..9bfe85ef0cf17f82ff0beca1623aa587a1cb0f1d --- /dev/null +++ b/MusicAnalyzer/Instruments/harp.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d775e11718dba8dd601e76bee5958aa02ed0d56c26481881d4c28226f7bd4477 +size 6137 diff --git a/MusicAnalyzer/Instruments/hat.ogg b/MusicAnalyzer/Instruments/hat.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f3fe717d060f6babe43e52ea1de220b30197cd37 --- /dev/null +++ b/MusicAnalyzer/Instruments/hat.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8139400f63c0bc7114e960826a11bb1cd561e8f0c01dce077dbc6424ae51ab6c +size 3880 diff --git a/MusicAnalyzer/Instruments/iron_xylophone.ogg b/MusicAnalyzer/Instruments/iron_xylophone.ogg new file mode 100644 index 0000000000000000000000000000000000000000..16f6a4f776445c2895c8e3c9beb8b42caaf270f9 --- /dev/null +++ b/MusicAnalyzer/Instruments/iron_xylophone.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a7ef8406f4ad4c5037aa1ecb7a9bf02cf34e86bb0bea46596eef147458b156a +size 9559 diff --git a/MusicAnalyzer/Instruments/pling.ogg b/MusicAnalyzer/Instruments/pling.ogg new file mode 100644 index 0000000000000000000000000000000000000000..56f8d223a67175bc636e1e8900dda057fcdfee7e --- /dev/null +++ b/MusicAnalyzer/Instruments/pling.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7e3638d4c0fba4614f24ba94d6380935ae2341536d76989e44f00ffd94a1459 +size 6274 diff --git a/MusicAnalyzer/Instruments/snare.ogg b/MusicAnalyzer/Instruments/snare.ogg new file mode 100644 index 0000000000000000000000000000000000000000..233fcda17191050ea0525fa21a2f3bf1e6b0f542 --- /dev/null +++ b/MusicAnalyzer/Instruments/snare.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a5168e70548dcddd5015204cec586365e611854e83bf634b42f2a6ddd8c3b7 +size 3969 diff --git a/MusicAnalyzer/Instruments/xylophone.ogg b/MusicAnalyzer/Instruments/xylophone.ogg new file mode 100644 index 0000000000000000000000000000000000000000..fcfd4183841d92ceec6e9341eb7530cac26881e3 --- /dev/null +++ b/MusicAnalyzer/Instruments/xylophone.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd9c7af0c25f7033bff3026f6133e33d7b4d26d5ab689b66376cae090df95dd0 +size 4551 diff --git a/MusicAnalyzer/RajatsMinecraftLibrary/amplitude.py b/MusicAnalyzer/RajatsMinecraftLibrary/amplitude.py new file mode 100644 index 0000000000000000000000000000000000000000..ea452d6a8e4254eeee21e76004a274f44a45694d --- /dev/null +++ b/MusicAnalyzer/RajatsMinecraftLibrary/amplitude.py @@ -0,0 +1,114 @@ +import matplotlib.pyplot as plt +import librosa + + +class AsfPosConverter: + @staticmethod + def getPosition(amplitude_dict, curr_amp): + closest_key = None + closest_diff = float("inf") + + for key, _ in amplitude_dict.items(): + diff = abs(float(key) - curr_amp) + if diff < closest_diff: + closest_key = key + closest_diff = diff + + return closest_key, amplitude_dict[closest_key] + + @staticmethod + def getAmplitude(amplitude_dict, curr_pos): + diff = float('inf') + res = None + for key, value in amplitude_dict.items(): + if abs(float(value) - curr_pos) < diff: + res = float(key) + diff = abs(float(value) - curr_pos) + return res + + # @staticmethod + # def analyze(audio_file): + # amplitude_list, _ = librosa.load(audio_file) + # plt.plot(amplitude_list) + # plt.show() + + # # IDENTIFY THE BELOW VALUES BY HOVERING THE CURSOR OVER MATPLOTLIB PLOT + # # amplitude_list = [0.155, 0.152, 0.149, 0.145, 0.143, 0.139, 0.134, 0.130, 0.121, 0.115, 0.108, 0.094, 0.089, 0.075, 0.054, 0.036, 0.016, 0] + + # # ----- AMPLITUDE LIST AVERAGING --------- + # # amplitude_list_copy = [1] + # # for i in range(1, len(amplitude_list)-1): + # # amplitude_list_copy.append(round((amplitude_list[i-1] + amplitude_list[i] + amplitude_list[i+1])/3, 5)) + # # # print("Max Amplitude:", max_amplitude) + # # amplitude_list_copy.append(0) + # # amplitude_list = amplitude_list_copy + # # ----- AMPLITUDE LIST SCALING --------- + # # amplitude_list_copy = [] + # # for amp in amplitude_list: + # # amplitude_list_copy.append(round(amp / amplitude_list[0], 4)) + # # print(amplitude_list_copy) + # # ----- AMPLITUDE LIST PLOTTING --------- + # # plt.plot(amplitude_list_copy) + # # plt.show() + # # --------------------------------------- + + # amplitude_list = [ + # 1, + # 0.99, + # 0.98, + # 0.97, + # 0.97, + # 0.96, + # 0.95, + # 0.95, + # 0.94, + # 0.93, + # 0.93, + # 0.92, + # 0.91, + # 0.91, + # 0.9, + # 0.89, + # 0.88, + # 0.87, + # 0.86, + # 0.845, + # 0.83, + # 0.815, + # 0.8, + # 0.79, + # 0.775, + # 0.76, + # 0.74, + # 0.72, + # 0.7, + # 0.68, + # 0.66, + # 0.64, + # 0.62, + # 0.6, + # 0.57, + # 0.54, + # 0.515, + # 0.49, + # 0.47, + # 0.43, + # 0.39, + # 0.35, + # 0.31, + # 0.27, + # 0.23, + # 0.19, + # 0.15, + # 0.11, + # 0, + # ] + # print(amplitude_list) + + +if __name__ == "__main__": + print( + "This is a library for Audio Manipulation via fourier transform made specificaly for minecraft audio production using note blocks" + ) + print("Author -: Rajat Bansal, IIT Mandi, B20123") + # AsfToPos.analyze('Sounds/Amplitude2.m4a') diff --git a/MusicAnalyzer/RajatsMinecraftLibrary/audio.py b/MusicAnalyzer/RajatsMinecraftLibrary/audio.py new file mode 100644 index 0000000000000000000000000000000000000000..146511b53a051106afa8d7de1aaa99a08cfd0e87 --- /dev/null +++ b/MusicAnalyzer/RajatsMinecraftLibrary/audio.py @@ -0,0 +1,186 @@ +import numpy as np +import librosa +import IPython.display as ipd +import matplotlib.pyplot as plt +# import audioread +# import time +# import soundfile as sf + +# def read_audio(path): +# try: +# if path[-4:] == '.ogg': +# y, sr_native = sf.read(path) +# else: +# buf = [] +# with audioread.audio_open(path) as input_file: +# sr_native = input_file.samplerate +# n_channels = input_file.channels + +# for frame in input_file: +# frame = (1.0 / float(1 << 15)) * np.frombuffer(frame, f" 1: +# y = y.reshape((-1, n_channels)).T +# y = np.mean(y, axis=tuple(range(y.ndim - 1))) +# y = librosa.resample(y, orig_sr=sr_native, target_sr=22050, res_type="soxr_hq") +# return y, 22050 + +# except Exception as e: +# print(f"Error reading audio file: {e}") +# return None, None + +class MyAudio: + def __init__(self, details, audioValues): + self.details = details + self.audioValues = audioValues + + @staticmethod + def combineTwoAudios(audio1, audio2): + details = audio1.details.copy() + details.extend(audio2.details) + audioValues = AudioManipulator.joinDiffAudiosValues( + [audio1.audioValues, audio2.audioValues] + ) + return MyAudio(details, audioValues) + + @staticmethod + def changeAudioToFFT(audio): + return MyAudio(audio.details, librosa.stft(audio.audioValues.copy())) + + @staticmethod + def compareTwoFFTAudios(audio1, audio2): + audio1Values = np.abs(audio1.audioValues) + audio2Values = np.abs(audio2.audioValues) + if audio1Values.shape[1] > audio2Values.shape[1]: + audio1Values, audio2Values = audio2Values, audio1Values + audio2Values = audio2Values[:, : audio1Values.shape[1]] + + norm = np.linalg.norm(audio1Values) * np.linalg.norm(audio2Values) + if norm == 0: + return 0 + return np.dot(audio1Values.flatten(), audio2Values.flatten()) / norm + + +class AudioManipulator: + # def __init__(self): + # self.n_mels = 128 * 2 + + @staticmethod + def addAudioValuesInDuration(audioValues1, audioValues2, timeSt, sr): + indexSt = min(len(audioValues1) - 1, int(timeSt / 1000 * sr)) + indexEd = min(len(audioValues1), indexSt + len(audioValues2)) + for index in range(indexSt, indexEd): + audioValues1[index] += audioValues2[index - indexSt] + return audioValues1 + + @staticmethod + def joinDiffAudiosValues(audiosValues): + mx = -1 + for i in range(len(audiosValues)): + mx = max(mx, len(audiosValues[i])) + for i in range(len(audiosValues)): + if len(audiosValues[i]) < mx: + audiosValues[i] = np.concatenate( + (audiosValues[i], np.zeros(int(mx - len(audiosValues[i])))) + ) + return np.sum(audiosValues, axis=0) + + @staticmethod + def getAudioValuesInterface(audioValues): + return ipd.Audio(audioValues) + + @staticmethod + def splitAudioValues(audioValues, sr, start_time, end_time): + audioValues = audioValues[ + int(sr * start_time / 1000) : int(sr * end_time / 1000) + ] + return audioValues + + @staticmethod + def shiftPitchOfAudioValues(audioValues, sr, pitch_shift): + audio_with_pitch_shift = librosa.effects.pitch_shift( + audioValues, sr=sr, n_steps=pitch_shift + ) + return audio_with_pitch_shift + + @staticmethod + def calculateAmplitudeShiftOfAudioValues(audioValues1, audioValues2, mode): + if mode == "Max": + peak_amplitude1 = np.max(np.abs(audioValues1)) + peak_amplitude2 = np.max(np.abs(audioValues2)) + elif mode == "Mean": + peak_amplitude1 = np.mean(np.abs(audioValues1)) + peak_amplitude2 = np.mean(np.abs(audioValues2)) + + scaling_factor = peak_amplitude1 / peak_amplitude2 + return round(scaling_factor, 2) + + @staticmethod + def getStftAndStftDb(audioValues): + stft = librosa.stft(audioValues) + stft_db = librosa.amplitude_to_db(abs(stft)) + return stft, stft_db + + @staticmethod + def getMelSpectogram(audioValues, sr): + mel_spec = librosa.feature.melspectrogram( + y=audioValues, sr=sr, n_mels=128*2 + ) + mel_spec_db = librosa.amplitude_to_db(mel_spec) # ref = np.max + return mel_spec, mel_spec_db + + @staticmethod + def getChromaGram(audioValues, sr): + chromaGram = librosa.feature.chroma_stft( + y=audioValues, sr=sr, hop_length=12 + ) + return chromaGram + + @staticmethod + def drawAudioValues(audioValues, sr): + plt.figure(figsize=(8.8, 3)) + plt.plot([(i + 1) / sr for i in range(len(audioValues))], audioValues) + plt.title("Raw Audio Example") + plt.show() + + @staticmethod + def drawAudioValuesSpectrum(audioValues, sr): + X, Xdb = AudioManipulator.getStft(audioValues) + plt.figure(figsize=(14, 5)) + librosa.display.specshow(Xdb, sr=sr, x_axis="time", y_axis="log") + plt.colorbar() + plt.show() + + def drawAudioValuesSpectrumNormalized(audioValues, sr): + X, Xdb = AudioManipulator.getStft(audioValues / audioValues.max() * 32767.00) + plt.figure(figsize=(14, 5)) + librosa.display.specshow(Xdb, sr=sr, x_axis="time", y_axis="log") + plt.colorbar() + plt.show() + + @staticmethod + def drawMelSpectrogram(audioValues, sr): + S, S_db_mel = AudioManipulator.getMelSpectogram(audioValues, sr) + + fig, ax = plt.subplots(figsize=(10, 3)) + img = librosa.display.specshow(S_db_mel, x_axis="time", y_axis="log", ax=ax) + ax.set_title("Mel Spectogram Example", fontsize=20) + fig.colorbar(img, ax=ax, format=f"%0.2f") + plt.show() + + @staticmethod + def drawChromaGram(audioValues, sr): + chromagram = AudioManipulator.getChromaGram(audioValues, sr) + plt.figure(figsize=(15, 5)) + librosa.display.specshow( + chromagram, x_axis="time", y_axis="chroma", hop_length=12, cmap="coolwarm" + ) + + +if __name__ == "__main__": + print( + "This is a library for Audio Manipulation via fourier transform made specificaly for minecraft audio production using note blocks" + ) + print("Author -: Rajat Bansal, IIT Mandi, B20123") diff --git a/MusicAnalyzer/RajatsMinecraftLibrary/minecraft.py b/MusicAnalyzer/RajatsMinecraftLibrary/minecraft.py new file mode 100644 index 0000000000000000000000000000000000000000..c93dbc2162d8e20ec4c3125de4dd50ef3de5ff8f --- /dev/null +++ b/MusicAnalyzer/RajatsMinecraftLibrary/minecraft.py @@ -0,0 +1,297 @@ +from enum import Enum +from .amplitude import AsfPosConverter + + +class space_state(Enum): + unavailable = 1 + available_without_connector = 2 + default_available = 3 + + +class spaceManager: + def __init__(self): + self.spaces_unavailable = {} + + def getSpaceState(self, coordinates): + if coordinates in self.spaces_unavailable: + return self.spaces_unavailable[coordinates] + return space_state.default_available + + def changeSpaceState(self, coordinates, state): + if coordinates in self.spaces_unavailable: + if self.spaces_unavailable[coordinates].value > state.value: + self.spaces_unavailable[coordinates] = state + else: + self.spaces_unavailable[coordinates] = state + + def getPlacementDetails(self, coordinates, pos, amplitude_dict): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + pos1 = pos + while self.getSpaceState((x, y, z + pos)) == space_state.unavailable: + pos += 1 + pos2 = pos + + pos = pos1 + while self.getSpaceState((x, y, z + pos)) == space_state.unavailable: + pos -= 1 + pos0 = pos + if (pos0 != 1) and ( + (pos2 == 49) + or ( + AsfPosConverter.getAmplitude(amplitude_dict, pos0) + - AsfPosConverter.getAmplitude(amplitude_dict, pos2) + < 2 * AsfPosConverter.getAmplitude(amplitude_dict, pos1) + ) + ): + pos = pos0 + else: + pos = pos2 + if ( + self.getSpaceState((x, y, z + pos)) + == space_state.available_without_connector + ): + return pos, False + elif self.getSpaceState((x, y, z + pos)) == space_state.default_available: + return pos, True + + def savePlacementDetails(self, coordinates, state): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + if state == "instant_repeater": + self.changeSpaceState((x, y, z), space_state.available_without_connector) + self.changeSpaceState((x, y, z + 1), space_state.unavailable) + self.changeSpaceState((x, y, z + 2), space_state.unavailable) + self.changeSpaceState( + (x, y, z + 3), space_state.available_without_connector + ) + else: + if state == "note_block_with_connector": + self.changeSpaceState((x, y, z), space_state.unavailable) + self.changeSpaceState( + (x, y, z - 1), space_state.available_without_connector + ) + self.changeSpaceState( + (x, y, z + 1), space_state.available_without_connector + ) + elif state == "note_block_without_connector": + self.changeSpaceState((x, y, z), space_state.unavailable) + + +class commandGenerator: + @staticmethod + def getCleanSpace(coordinates, l, b, h): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + res = f"fill {x} {y} {z} {x + l} {y + b} {z + h} air\n" + return res + + @staticmethod + def getInstantRepeater(coordinates): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + res = f""" +#---------INSTANT_REPEATER----------- +setblock {x} {y} {z} redstone_lamp +setblock {x} {y + 1} {z} sticky_piston[facing=south] +setblock {x} {y + 1} {z + 1} redstone_block +setblock {x} {y} {z + 2} sticky_piston[facing=north,extended=true] +setblock {x} {y} {z + 3} redstone_lamp +setblock {x} {y + 1} {z + 3} redstone_wire +#------------------------------------ + """ + return res + + @staticmethod + def getNoteBlock(coordinates, inc, block, pos, place_connector): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + res = "" + if place_connector: + res += f""" +#--------------NOTE_BLOCK-{pos}------------------- +setblock {x} {y-1} {z + pos} glass +setblock {x} {y} {z + pos} redstone_wire""" + else: + res += f""" +#--------------NOTE_BLOCK-{pos}-------------------""" + res += f""" +setblock {x + inc} {y - 2} {z + pos} glass +setblock {x + inc} {y - 1} {z + pos} {block['block_name']} +setblock {x + inc} {y} {z + pos} note_block[note={block['note']}] +#----------------------------------------------- + """ + return res + + @staticmethod + def getMainRepeaterAndRedstoneLine(coordinates, tm, inc): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + dir = "west" if inc == 1 else "east" + res = f""" +#------------------------------------{tm / 1000}----------------------------------------- +setblock {x} {y - 1} {z} glass +setblock {x} {y} {z} repeater[delay=1,facing={dir}] + +fill {x + inc} {y - 1} {z} {x + inc} {y - 1} {z + 48} glass +fill {x + inc} {y} {z} {x + inc} {y} {z + 48} redstone_wire + +setblock {x + 2*inc} {y - 1} {z} glass +setblock {x + 2*inc} {y} {z} redstone_wire + """ + return res + + @staticmethod + def getUpperFloorConnection(coordinates, inc): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + res = f""" +#--------------UPPER-FLOOR-CONNECTION------------------- +setblock {x} {y-1} {z} glass +setblock {x} {y} {z} redstone_wire +setblock {x + inc} {y - 1} {z} glass +setblock {x + inc} {y} {z} redstone_wire +setblock {x + inc} {y} {z - 1} glass +setblock {x + inc} {y + 1} {z - 1} redstone_wire +setblock {x + inc} {y + 1} {z - 2} glass +setblock {x + inc} {y + 2} {z - 2} redstone_wire +setblock {x + 2 * inc} {y + 2} {z - 2} glass +setblock {x + 2 * inc} {y + 3} {z - 2} redstone_wire +setblock {x + 2 * inc} {y + 3} {z - 1} glass +setblock {x + 2 * inc} {y + 4} {z - 1} redstone_wire +setblock {x + 2 * inc} {y + 3} {z} glass +setblock {x + 2 * inc} {y + 4} {z} redstone_wire +#------------------------------------------------------- + """ + return res + + @staticmethod + def getMineCartRailsStarter(coordinates): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + res = f""" +#--------------MINECART-RAIL-STARTER------------------- +setblock {x - 1} {y} {z} redstone_wire +setblock {x - 2} {y} {z} redstone_wire +setblock {x - 2} {y} {z - 1} redstone_wire +setblock {x - 2} {y} {z - 2} redstone_wire +setblock {x - 2} {y} {z - 3} redstone_wire +setblock {x - 2} {y} {z - 4} redstone_wire +setblock {x - 1} {y} {z - 4} redstone_wire +setblock {x} {y} {z-4} redstone_wire +setblock {x + 1} {y} {z - 4} redstone_wire +setblock {x + 2} {y} {z - 4} repeater[delay=1,facing=east] +setblock {x + 3} {y} {z - 4} redstone_wire +setblock {x + 4} {y} {z - 4} redstone_wire +setblock {x + 5} {y} {z - 4} redstone_wire +setblock {x + 6} {y} {z - 4} redstone_wire +setblock {x + 7} {y} {z - 4} redstone_wire +setblock {x + 8} {y} {z - 4} redstone_wire +setblock {x + 9} {y} {z - 4} redstone_wire +setblock {x + 10} {y} {z - 4} redstone_wire +setblock {x + 11} {y} {z - 4} redstone_wire +setblock {x + 12} {y} {z - 4} repeater[delay=1,facing=east] +setblock {x + 13} {y} {z - 4} redstone_wire +setblock {x + 14} {y} {z - 4} redstone_wire +setblock {x + 15} {y} {z - 4} redstone_wire +setblock {x + 16} {y} {z - 4} redstone_wire +setblock {x + 17} {y} {z - 4} redstone_wire +setblock {x + 18} {y} {z - 4} redstone_wire +setblock {x + 19} {y} {z - 4} stone_button[face=floor] +setblock {x + 19} {y} {z - 3} redstone_wire +setblock {x + 19} {y} {z - 2} smooth_quartz +setblock {x + 20} {y} {z - 2} powered_rail +summon minecart {x + 20} {y} {z - 2} +setblock {x + 21} {y} {z - 2} rail +setblock {x + 22} {y} {z - 2} powered_rail +#------------------------------------------------------- + """ + return res + + @staticmethod + def getMineCartRailsEnder(coordinates, inc): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + + if inc == 1: + x = x + 23 + z = z - 2 + else: + x = x - 26 + z = z - 1 + + res = f""" +#--------------MINECART-RAIL-ENDER------------------- +setblock {x} {y - 1} {z - 1} smooth_quartz +setblock {x} {y} {z - 1} redstone_torch +setblock {x} {y - 1} {z} smooth_quartz +setblock {x} {y} {z} powered_rail +setblock {x + inc} {y - 1} {z} smooth_quartz +setblock {x + inc} {y} {z} rail +setblock {x + 2 * inc} {y - 1} {z} smooth_quartz +setblock {x + 2 * inc} {y} {z} powered_rail +summon minecart {x + 2 * inc} {y} {z} +setblock {x + 3 * inc} {y - 1} {z} smooth_quartz +setblock {x + 3 * inc} {y} {z} smooth_quartz +#------------------------------------------------------- + """ + return res + + @staticmethod + def getMineCartRails(coordinates, inc): + x, y, z = coordinates[0], coordinates[1], coordinates[2] + + if inc == 1: + x = x + 23 + z = z - 2 + else: + x = x - 26 + z = z - 1 + + res = f""" +#--------------MINECART-RAIL-SUPPORT------------------- +setblock {x} {y - 1} {z - 1} smooth_quartz +setblock {x} {y} {z - 1} redstone_torch +setblock {x} {y - 1} {z} smooth_quartz +setblock {x} {y} {z} powered_rail +setblock {x + inc} {y} {z} smooth_quartz +setblock {x + inc} {y + 1} {z} powered_rail +setblock {x + 2 * inc} {y + 1} {z} smooth_quartz +setblock {x + 2 * inc} {y + 2} {z} powered_rail +setblock {x + 3 * inc} {y + 2} {z} smooth_quartz +setblock {x + 3 * inc} {y + 3} {z} powered_rail +setblock {x + 4 * inc} {y + 2} {z} smooth_quartz +setblock {x + 4 * inc} {y + 3} {z} powered_rail +setblock {x + 5 * inc} {y + 2} {z} smooth_quartz +setblock {x + 5 * inc} {y + 3} {z} powered_rail +setblock {x + 6 * inc} {y + 2} {z} smooth_quartz""" + + if inc == 1: + res += f""" +setblock {x + 6 * inc} {y + 3} {z} rail +setblock {x + 6 * inc} {y + 2} {z + inc} smooth_quartz +setblock {x + 6 * inc} {y + 3} {z + inc} rail""" + else: + res += f""" +setblock {x + 6 * inc} {y + 3} {z} powered_rail +setblock {x + 7 * inc} {y + 2} {z} smooth_quartz +setblock {x + 7 * inc} {y + 3} {z} rail +setblock {x + 7 * inc} {y + 2} {z + inc} smooth_quartz +setblock {x + 7 * inc} {y + 3} {z + inc} rail +setblock {x + 6 * inc} {y + 2} {z + inc} smooth_quartz +setblock {x + 6 * inc} {y + 3} {z + inc} powered_rail""" + + res += f""" +setblock {x + 5 * inc} {y + 2} {z + inc} smooth_quartz +setblock {x + 5 * inc} {y + 3} {z + inc} powered_rail +setblock {x + 4 * inc} {y + 3} {z + inc} smooth_quartz +setblock {x + 4 * inc} {y + 4} {z + inc} powered_rail +setblock {x + 3 * inc} {y + 3} {z + inc} smooth_quartz +setblock {x + 3 * inc} {y + 4} {z + inc} powered_rail +setblock {x + 2 * inc} {y + 3} {z + inc} smooth_quartz +setblock {x + 2 * inc} {y + 4} {z + inc} powered_rail +setblock {x + 1 * inc} {y + 3} {z + inc} smooth_quartz +setblock {x + 1 * inc} {y + 4} {z + inc} powered_rail +#------------------------------------------------------- + """ + return res + + +if __name__ == "__main__": + print( + "This is a library for Audio Manipulation via fourier transform made specificaly for minecraft audio production using note blocks" + ) + print("Author -: Rajat Bansal, IIT Mandi, B20123") + print("Sample Command -:") + print(commandGenerator.getCleanSpace((0, 0, 0), 1, 1, 1)) diff --git a/MusicAnalyzer/RajatsMinecraftLibrary/test.py b/MusicAnalyzer/RajatsMinecraftLibrary/test.py new file mode 100644 index 0000000000000000000000000000000000000000..183c6096e4dee5b7c0da2414f5c5358618de017f --- /dev/null +++ b/MusicAnalyzer/RajatsMinecraftLibrary/test.py @@ -0,0 +1,64 @@ +# from rajatsLibrary.audio import AudioManipulator +# import librosa +# import matplotlib.pyplot as plt +# import numpy as np + + +# testAudio, _ = librosa.load('Base sounds correct.m4a') +# print(len(testAudio)) +# # testAudio = testAudio[:6000] +# # audios = [] +# # for i in range(24): +# # audios.extend(AudioManipulator.shiftPitchOfAudioValues(testAudio, _, i)) +# # cleanAudio = [] +# # noiceOnce = testAudio[:20000] +# # mxNoice = max(abs(noiceOnce)) + +# # for audioPoint in testAudio: +# # if(abs(audioPoint) > mxNoice): +# # cleanAudio.append(audioPoint) +# # else: +# # cleanAudio.append(0) + +# # cleanAudio = cleanAudio[20000:] +# # cleanAudio = cleanAudio[:-20000] + +# # noice = [] +# # while len(noice) < len(testAudio): +# # noice.extend(noiceOnce) +# # print(len(noice)) + +# # testAudio = testAudio - noice[:len(testAudio)] +# plt.plot(testAudio) +# # plt.xticks(np.arange(0, len(testAudio)+1, 16000.0)) +# plt.grid() +# plt.show() + + + + +# import os +# import librosa +# import matplotlib.pyplot as plt + + +# audios = [] +# instruments = os.listdir('Instruments copy') +# instruments = sorted(instruments) +# instruments1 = instruments[:7] +# instruments2 = instruments[7:] +# instruments2.extend(instruments1) +# instruments = instruments2 +# print(instruments) +# scaling_factors = [0.92, 1.94, 3.16, 1.41, 0.633, 0.775, 3.954, 1.712, 0.861, 1.327, 3.768, 1.013, 2.307, 0.645, 10.482, 1.255] +# i = 0 +# for instrument in instruments: +# audioValues, _ = librosa.load('Instruments copy/' + instrument) +# audioValues = audioValues * scaling_factors[i] +# audios.extend(audioValues) +# audios.extend([0] * 10000) +# i+=1 + +# plt.plot(audios) +# plt.grid() +# plt.show() \ No newline at end of file diff --git a/MusicAnalyzer/commandGenerator.py b/MusicAnalyzer/commandGenerator.py new file mode 100644 index 0000000000000000000000000000000000000000..c2fe1184f77454bf04444142b01cac48d7737d6a --- /dev/null +++ b/MusicAnalyzer/commandGenerator.py @@ -0,0 +1,177 @@ +import pickle as pkl +import json +from .RajatsMinecraftLibrary.minecraft import commandGenerator +import configparser +import argparse +from .RajatsMinecraftLibrary.amplitude import AsfPosConverter +from .RajatsMinecraftLibrary.minecraft import spaceManager + +def getBlockDetails( + data, music_box_dict, amplitude_dict, pitch_mapping_shift, sim_thresh +): + res = [] + for beat in data[1:]: + if beat[1]["similarity"] > sim_thresh: + tm_value = beat[0] + beat_details = beat[1] + beat_instruments = beat_details["combination"] + curr_instruments_details = [] + for instrument in beat_instruments: + asf = round(float(instrument["ASF"]), 3) + pos_details = AsfPosConverter.getPosition(amplitude_dict, asf) + curr_instruments_details.append( + { + "block_name": music_box_dict[instrument["instrument"]], + "note": pitch_mapping_shift + instrument["pitchShift"], + "position": int(pos_details[1]), + } + ) + curr_instruments_details = sorted( + curr_instruments_details, key=lambda x: x["position"] + ) + res.append((tm_value, curr_instruments_details)) + return res + + +def generateCommands( + data, + music_box_dict, + amplitude_dict, + hearable_range, + one_hundred_milli_horizontal_gap, + starting_coordinates, + one_floor_vertical_gap, + instant_repeater_zs, + pitch_mapping_shift, + sim_thresh +): + startingX, startingY, startingZ = ( + starting_coordinates[0], + starting_coordinates[1], + starting_coordinates[2], + ) + myCommandGenerator = commandGenerator() + space_manager = spaceManager() + + block_details = getBlockDetails( + data, music_box_dict, amplitude_dict, pitch_mapping_shift, sim_thresh + ) + batch_size = int(hearable_range / one_hundred_milli_horizontal_gap) + floor_level = 0 + output = myCommandGenerator.getCleanSpace( + starting_coordinates, hearable_range, hearable_range, hearable_range + ) + output += myCommandGenerator.getMineCartRailsStarter(starting_coordinates) + x = startingX + + for i in range(0, len(block_details), batch_size): + batch = block_details[i : i + batch_size] + inc = 1 if floor_level % 2 == 0 else -1 + if len(batch) == batch_size: + output += myCommandGenerator.getMineCartRails( + (x, startingY + floor_level * one_floor_vertical_gap, startingZ), inc + ) + else: + output += myCommandGenerator.getMineCartRailsEnder( + (x, startingY + floor_level * one_floor_vertical_gap, startingZ), inc + ) + for oneMilliNotes in batch: + tm = oneMilliNotes[0] + note_blocks = oneMilliNotes[1] + current_height = startingY + floor_level * one_floor_vertical_gap + output += myCommandGenerator.getMainRepeaterAndRedstoneLine( + (x, current_height, startingZ), tm, inc + ) + space_manager.savePlacementDetails( + (x + inc, current_height, startingZ + instant_repeater_zs[0]), + "instant_repeater", + ) + output += myCommandGenerator.getInstantRepeater( + (x + inc, current_height, startingZ + instant_repeater_zs[0]) + ) + space_manager.savePlacementDetails( + (x + inc, current_height, startingZ + instant_repeater_zs[1]), + "instant_repeater", + ) + output += myCommandGenerator.getInstantRepeater( + (x + inc, current_height, startingZ + instant_repeater_zs[1]) + ) + for note_block in note_blocks: + pos, place_connector = space_manager.getPlacementDetails( + (x + inc, current_height, startingZ), + note_block["position"], + amplitude_dict, + ) + state = ( + "note_block_with_connector" + if place_connector + else "note_block_without_connector" + ) + space_manager.savePlacementDetails( + (x + inc, current_height, startingZ + pos), state + ) + output += myCommandGenerator.getNoteBlock( + (x + 2 * inc, current_height, startingZ), + inc, + note_block, + pos, + place_connector, + ) + x += 3 * inc + if len(batch) == batch_size: + output += myCommandGenerator.getUpperFloorConnection( + (x, current_height, startingZ), inc + ) + x += inc + floor_level += 1 + return output + + +# if __name__ == "__main__": +# config = configparser.ConfigParser() +# config.read("config.ini") + +# parser = argparse.ArgumentParser( +# description="Command generator for minecraft note blocks" +# ) +# parser.add_argument("-f", "--file", help="Specify the file name for processing") +# parser.add_argument("-c", "--coordinates", help="Starting coordinatest") +# args = parser.parse_args() +# music_box_dict = json.loads(config["MinecraftSettings"]["music_box_dict"]) +# amplitude_dict = json.loads(config["MinecraftSettings"]["amplitude_dict"]) +# pitch_mapping_shift = int(config["MinecraftSettings"]["pitch_mapping_shift"]) +# sim_thresh = float(config["MinecraftSettings"]['sim_thresh']) +# instant_repeater_zs = [int(_) for _ in config["MinecraftSettings"]["instant_repeater_zs"].split(",")] +# hearable_range = int(config["MinecraftSettings"]["hearable_range"]) +# one_floor_vertical_gap = int(config["MinecraftSettings"]["one_floor_vertical_gap"]) +# one_hundred_milli_horizontal_gap = int(config["MinecraftSettings"]["100ms_horizontal_gap"]) +# results_path = config["MinecraftSettings"]["results_path"] +# target_file = args.file + +# if args.file and args.coordinates: +# starting_coordinates = [int(_) for _ in args.coordinates.split(",")] +# pickle_file_path = f"{results_path}pkl/{target_file}.pkl" +# with open(pickle_file_path, "rb") as f: +# data = pkl.load(f) +# commands = generateCommands( +# data, +# music_box_dict, +# amplitude_dict, +# hearable_range, +# one_hundred_milli_horizontal_gap, +# starting_coordinates, +# one_floor_vertical_gap, +# instant_repeater_zs, +# pitch_mapping_shift, +# sim_thresh, +# ) +# # with open(f"{results_path}musicCommand_{target_file}.mcfunction", "w") as f: +# with open(f"{results_path}commands/{target_file.lower()}.mcfunction", "w") as f: +# f.write(commands) +# else: +# print( +# "Usage - python commandGenerator.py -f -c " +# ) + +# # plt.scatter([i for i in range(len(asfValues))], asfValues) +# # plt.show() diff --git a/MusicAnalyzer/config.ini b/MusicAnalyzer/config.ini new file mode 100644 index 0000000000000000000000000000000000000000..bd7282d219df29335a6b9639792180afb6c9ab6d --- /dev/null +++ b/MusicAnalyzer/config.ini @@ -0,0 +1,116 @@ +# config.ini + +[AudioSettings] +instrumentsPath = Instruments/ +sr = 22050 +instruments_dict = { + "cow_bell.ogg": [-12,12], + "iron_xylophone.ogg": [-12,12], + "bit.ogg": [-12,12], + "flute.ogg": [-12,12], + "didgeridoo.ogg": [-12,12], + "bdrum.ogg": [-12,12], + "bell.ogg": [-12,12], + "pling.ogg": [-12,12], + "snare.ogg": [-12,12], + "banjo.ogg": [-12,12], + "harp.ogg": [-12,12], + "guitar.ogg": [-12,12], + "xylophone.ogg": [-12,12], + "bass.ogg": [-12,12], + "chimes.ogg": [-12,12], + "hat.ogg": [-12,12]} +scaling_dict = { + "cow_bell.ogg": 0.92, + "iron_xylophone.ogg": 1.94, + "bit.ogg": 3.16, + "flute.ogg": 1.41, + "didgeridoo.ogg": 0.633, + "bdrum.ogg": 0.775, + "bell.ogg": 3.954, + "pling.ogg": 1.712, + "snare.ogg": 0.861, + "banjo.ogg": 1.327, + "harp.ogg": 3.768, + "guitar.ogg": 1.013, + "xylophone.ogg": 2.307, + "bass.ogg": 0.645, + "chimes.ogg": 10.482, + "hat.ogg": 1.255} +initialBestMatchesLength = 7 +binLength = 100 +simThresh = 0.70 + +[MinecraftSettings] +results_path = Results/ +sim_thresh = 0.70 +pitch_mapping_shift = 12 +100ms_horizontal_gap = 3 +instant_repeater_zs = 15,33 +hearable_range = 48 +one_floor_vertical_gap = 4 +music_box_dict = { + "cow_bell.ogg": "soul_sand", + "iron_xylophone.ogg": "iron_block", + "bit.ogg": "emerald_block", + "flute.ogg": "clay", + "didgeridoo.ogg": "pumpkin", + "bdrum.ogg": "stone", + "bell.ogg": "gold_block", + "pling.ogg": "glowstone", + "snare.ogg": "sand", + "banjo.ogg": "hay_block", + "harp.ogg": "dirt", + "guitar.ogg": "green_wool", + "xylophone.ogg": "bone_block", + "bass.ogg": "mangrove_wood", + "chimes.ogg": "packed_ice", + "hat.ogg": "glass"} +amplitude_dict = { + "0.99": "2", + "0.98": "3", + "0.975": "4", + "0.97": "5", + "0.96": "6", + "0.955": "7", + "0.95": "8", + "0.94": "9", + "0.935": "10", + "0.93": "11", + "0.92": "12", + "0.915": "13", + "0.91": "14", + "0.9": "15", + "0.89": "16", + "0.88": "17", + "0.87": "18", + "0.86": "19", + "0.845": "20", + "0.83": "21", + "0.815": "22", + "0.8": "23", + "0.79": "24", + "0.775": "25", + "0.76": "26", + "0.74": "27", + "0.72": "28", + "0.7": "29", + "0.68": "30", + "0.66": "31", + "0.64": "32", + "0.62": "33", + "0.6": "34", + "0.57": "35", + "0.54": "36", + "0.515": "37", + "0.49": "38", + "0.47": "39", + "0.43": "40", + "0.39": "41"} + ; "0.35": "42", + ; "0.31": "43", + ; "0.27": "44", + ; "0.23": "45", + ; "0.19": "46", + ; "0.15": "47", + ; "0.11": "48"} diff --git a/MusicAnalyzer/musicAnalyzer.py b/MusicAnalyzer/musicAnalyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..9435d92e323be9fe2eed2cdcc10ccd4b9d32e8a5 --- /dev/null +++ b/MusicAnalyzer/musicAnalyzer.py @@ -0,0 +1,205 @@ +from .RajatsMinecraftLibrary.audio import MyAudio, AudioManipulator +import librosa +import configparser +import json +import pickle as pkl +import argparse +from collections import deque +import numpy as np +import soundfile as sf +import os + +config = configparser.ConfigParser() +script_dir = os.path.dirname(os.path.abspath(__file__)) + +config.read(os.path.join(script_dir, 'config.ini')) + + +def preProcess( + mainAudioValues, + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + sounds_file_path, + amplitudeMode, +): + startTime = 0 + result = [] + resAudioValues = np.zeros(len(mainAudioValues)) + # simValues = [] + while startTime < 1000 * len(mainAudioValues) / sr: + # print(startTime, end=",") + resAudio = MyAudio( + [{"fileName": "resFile", "pitchShift": 0, "ASF": 1}], + AudioManipulator.splitAudioValues( + resAudioValues, sr, startTime, startTime + binLength + ), + ) + mainAudio = MyAudio( + [{"fileName": "targetFile", "pitchShift": 0, "ASF": 1}], + AudioManipulator.splitAudioValues( + mainAudioValues, sr, startTime, startTime + binLength + ), + ) + + ########################### Finding initial best matches ######################## + initialBestMatches = [] + for instrument in instruments_dict: + rng = instruments_dict[instrument] + audioValues, sr = librosa.load(os.path.join(script_dir, "Instruments/" + instrument)) + audioValues *= scaling_dict[instrument] + for pitchShift in range(rng[0], rng[1] + 1): + asf = AudioManipulator.calculateAmplitudeShiftOfAudioValues( + mainAudio.audioValues, AudioManipulator.shiftPitchOfAudioValues( + audioValues, sr, pitchShift + ), amplitudeMode + ) + pitchShiftedAudio = MyAudio( + [{"instrument": instrument, "pitchShift": pitchShift, "ASF": asf}], + AudioManipulator.shiftPitchOfAudioValues( + audioValues, sr, pitchShift + ) + * asf, + ) + combinedAudio = MyAudio.combineTwoAudios(resAudio, pitchShiftedAudio) + sim = MyAudio.compareTwoFFTAudios( + MyAudio.changeAudioToFFT(mainAudio), + MyAudio.changeAudioToFFT(combinedAudio), + ) + initialBestMatches.append( + { + "similarity": round(sim, 2), + "instrument": instrument, + "pitchShift": pitchShift, + "ASF": asf, + } + ) + initialBestMatches = sorted( + initialBestMatches, key=lambda x: x["similarity"], reverse=True + ) + + ###################### Making all combinations and finding there similarities ###################### + combinationsQueue = deque() + ogAudios = [] + mxIndex = initialBestMatchesLength + for idx, note in enumerate(initialBestMatches[:initialBestMatchesLength]): + audioValues, _ = librosa.load(os.path.join(script_dir, f'Instruments/{note["instrument"]}')) + audioValues *= scaling_dict[note["instrument"]] + audio = MyAudio( + [ + { + "instrument": note["instrument"], + "pitchShift": note["pitchShift"], + "ASF": note["ASF"], + } + ], + AudioManipulator.shiftPitchOfAudioValues( + audioValues, sr, note["pitchShift"] + ) + * note["ASF"], + ) + + ogAudios.append(audio) + combinationsQueue.append({"idx": idx, "audio": audio}) + combinationSimilarities = [] + while len(combinationsQueue): + combination = combinationsQueue.popleft() + # print("COM", combination[1].details) + sim = MyAudio.compareTwoFFTAudios( + MyAudio.changeAudioToFFT(mainAudio), + MyAudio.changeAudioToFFT( + MyAudio.combineTwoAudios(resAudio, combination["audio"]) + ), + ) + combinationSimilarities.append( + { + "similarity": round(sim, 2), + "combination": combination["audio"].details, + } + ) + for combinableAudioId in range(combination["idx"] + 1, mxIndex): + combinationsQueue.append( + { + "idx": combinableAudioId, + "audio": MyAudio.combineTwoAudios( + combination["audio"], ogAudios[combinableAudioId] + ), + } + ) + combinationSimilarities = sorted( + combinationSimilarities, key=lambda x: x["similarity"], reverse=True + ) + + ############################# Making resulting audio from optimum combination ############################# + bestMatch = combinationSimilarities[0] + result.append((startTime, bestMatch)) + if bestMatch["similarity"] >= simThresh: + for instrumentDetails in bestMatch["combination"]: + instrumentAudioValues, _ = librosa.load( + os.path.join(script_dir, f'Instruments/{instrumentDetails["instrument"]}') + ) + instrumentAudioValues *= scaling_dict[instrumentDetails["instrument"]] + + instrumentAudioValues = ( + AudioManipulator.shiftPitchOfAudioValues( + instrumentAudioValues, sr, instrumentDetails["pitchShift"] + ) + * instrumentDetails["ASF"] + ) + resAudioValues = AudioManipulator.addAudioValuesInDuration( + resAudioValues, instrumentAudioValues, startTime, sr + ) + # print(bestMatch) + # simValues.append(bestMatch["similarity"]) + + if startTime % 1000 == 0: + # AudioManipulator.drawAudioValues(mainAudioValues, sr) + # AudioManipulator.drawAudioValues(resAudioValues, sr) + sf.write( + sounds_file_path, resAudioValues, sr + ) + startTime += binLength + + # for simValue in sorted(simValues, reverse=True): + # print(int(simValue * 100), end=',') + # print() + return result + + +# if __name__ == "__main__": +# parser = argparse.ArgumentParser(description="Music analyzer for minecraft note blocks") +# parser.add_argument("-m", "--mode", help="Specify the mode. or ") +# parser.add_argument("-f", "--file", help="Specify the file path for processing") +# parser.add_argument("-o", "--output", help="Specify the result path for saving") +# args = parser.parse_args() +# musicFilePath = args.file +# outputFolderPath = args.output +# amplitudeMode = args.mode + +# if musicFilePath and amplitudeMode: +# sr = int(config["AudioSettings"]["sr"]) +# instruments_dict = json.loads(config["AudioSettings"]["instruments_dict"]) +# scaling_dict = json.loads(config["AudioSettings"]["scaling_dict"]) +# initialBestMatchesLength = int(config["AudioSettings"]["initialBestMatchesLength"]) +# binLength = int(config["AudioSettings"]["binLength"]) +# simThresh = float(config["AudioSettings"]["simThresh"]) +# mainAudioValues, _ = librosa.load(f"{musicFilePath}") +# sounds_file_path = os.path.join(outputFolderPath, "uEim193#3ka.mp3"), +# preProcessingResults = preProcess( +# mainAudioValues, +# sr, +# instruments_dict, +# scaling_dict, +# initialBestMatchesLength, +# simThresh, +# binLength, +# sounds_file_path, +# amplitudeMode, +# ) +# with open(os.path.join(outputFolderPath, f"pkl/{musicFilePath.split("/")[-1].split(".")[0]}{amplitudeMode}.pkl"), "wb") as f: +# pkl.dump(preProcessingResults, f) +# else: +# print("Usage - python musicAnalyzer.py -f -o -m ") diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..be842f9f643996eb40390c8ab3c82bbf52866de7 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +--- +title: Minecraft Noteblock Music Generatore +emoji: 👀 +colorFrom: pink +colorTo: purple +sdk: docker +pinned: false +--- + +Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..81981f4378b31602064943a75454522be5d729e2 --- /dev/null +++ b/app.py @@ -0,0 +1,115 @@ +from flask import Flask, request, render_template +import os +import views +import warnings +import configparser +import json + +warnings.filterwarnings( + "ignore", message="n_fft=2048 is too large for input signal of length" +) +warnings.filterwarnings("ignore", message="librosa.core.audio.__audioread_load") +warnings.filterwarnings( + "ignore", message="PySoundFile failed. Trying audioread instead." +) + +config = configparser.ConfigParser() +script_dir = os.path.dirname(os.path.abspath(__file__)) +config.read(os.path.join(script_dir, "config.ini")) + +config = config["AppSettings"] +app = Flask(__name__) + + +@app.route("/") +def index(): + audios = views.index_view(script_dir) + return render_template("index.html", page="index", audios=audios) + + +# @app.route('/about') +# def about(): +# return render_template("about.html", page = "about") + +# @app.route('/manual') +# def manual(): +# return render_template("manual.html", page = "manual") + +# # # Define an API endpoint +# # @app.route('/api/data', methods=['GET']) +# # def get_data(): +# # data = { +# # 'message': 'This is a basic Flask API endpoint!', +# # 'data': [1, 2, 3, 4, 5] +# # } +# # return jsonify(data) + + +@app.route("/api/process-music", methods=["POST"]) +def process_music(): + simThresh = float(request.form["simThresh"]) + amplitudeMode = request.form["amplitudeMode"] + instruments_dict = { + f"{ogi}.ogg": [-12, 12] + for ogi in json.loads(config["instruments_name"]) + if ogi in request.form + } + parallel_processes_count = int(config["parallelProcessesCount"]) + return views.process_music_view( + request.files, + json.loads(config["ALLOWED_EXTENSIONS"]), + os.path.join(script_dir, config["AUDIOS_DIR"]), + int(config["sr"]), + instruments_dict, + json.loads(config["scaling_dict"]), + int(config["initialBestMatchesLength"]), + simThresh, + int(config["bin_length"]), + amplitudeMode, + parallel_processes_count, + ) + + +# # @app.route("/api/get-processed-music", methods=["GET"]) +# # def get_processed_music(): +# # return views.get_processed_music_view( +# # os.path.join(script_dir, config["FILES_FOLDER"]) +# # ) + + +@app.route("/api/get-commands", methods=["POST"]) +def get_commands(): + music_box_dict = json.loads(config["music_box_dict"]) + amplitude_dict = json.loads(config["amplitude_dict"]) + pitch_mapping_shift = int(config["pitch_mapping_shift"]) + instant_repeater_zs = [int(_) for _ in config["instant_repeater_zs"].split(",")] + hearable_range = int(config["hearable_range"]) + one_floor_vertical_gap = int(config["one_floor_vertical_gap"]) + one_hundred_milli_horizontal_gap = int(config["100ms_horizontal_gap"]) + + audioId = request.form.get("audioId", type=str) + starting_coordinates = [ + int(_) for _ in request.form.get("startingCoordinates").split(" ") + ] + sim_thresh = request.form.get("simThresh", type=float) + amplitude_mode = request.form.get("commandsAmplitudeMode", type=str) + + return views.get_commands_view( + os.path.join( + os.path.join(os.path.join(script_dir, config["AUDIOS_DIR"]), audioId), + f"result-{amplitude_mode}.pkl", + ), + starting_coordinates, + music_box_dict, + amplitude_dict, + pitch_mapping_shift, + sim_thresh, + instant_repeater_zs, + hearable_range, + one_floor_vertical_gap, + one_hundred_milli_horizontal_gap, + ) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=7860, debug=True) diff --git a/config.ini b/config.ini new file mode 100644 index 0000000000000000000000000000000000000000..97e98b77d5e1692cdda9521eec66fa27b0b4222a --- /dev/null +++ b/config.ini @@ -0,0 +1,98 @@ +# config.ini + +[AppSettings] +AUDIOS_DIR = Audios +ALLOWED_EXTENSIONS = ["mp3", "wav", "ogg", "flac", "aac", "m4a"] + +parallelProcessesCount = 5 +instruments_name = ["cow_bell", "iron_xylophone", "bit", "flute", "didgeridoo", "bdrum", "bell", "pling", "snare", "banjo", "harp", "guitar", "xylophone", "bass", "chimes", "hat"] +sr = 22050 +scaling_dict = { + "cow_bell.ogg": 0.92, + "iron_xylophone.ogg": 1.94, + "bit.ogg": 3.16, + "flute.ogg": 1.41, + "didgeridoo.ogg": 0.633, + "bdrum.ogg": 0.775, + "bell.ogg": 3.954, + "pling.ogg": 1.712, + "snare.ogg": 0.861, + "banjo.ogg": 1.327, + "harp.ogg": 3.768, + "guitar.ogg": 1.013, + "xylophone.ogg": 2.307, + "bass.ogg": 0.645, + "chimes.ogg": 10.482, + "hat.ogg": 1.255} +initialBestMatchesLength = 7 +bin_length = 100 +pitch_mapping_shift = 12 +100ms_horizontal_gap = 3 +instant_repeater_zs = 15,33 +hearable_range = 48 +one_floor_vertical_gap = 4 +music_box_dict = { + "cow_bell.ogg": "soul_sand", + "iron_xylophone.ogg": "iron_block", + "bit.ogg": "emerald_block", + "flute.ogg": "clay", + "didgeridoo.ogg": "pumpkin", + "bdrum.ogg": "stone", + "bell.ogg": "gold_block", + "pling.ogg": "glowstone", + "snare.ogg": "sand", + "banjo.ogg": "hay_block", + "harp.ogg": "dirt", + "guitar.ogg": "green_wool", + "xylophone.ogg": "bone_block", + "bass.ogg": "mangrove_wood", + "chimes.ogg": "packed_ice", + "hat.ogg": "glass"} +amplitude_dict = { + "0.99": "2", + "0.98": "3", + "0.975": "4", + "0.97": "5", + "0.96": "6", + "0.955": "7", + "0.95": "8", + "0.94": "9", + "0.935": "10", + "0.93": "11", + "0.92": "12", + "0.915": "13", + "0.91": "14", + "0.9": "15", + "0.89": "16", + "0.88": "17", + "0.87": "18", + "0.86": "19", + "0.845": "20", + "0.83": "21", + "0.815": "22", + "0.8": "23", + "0.79": "24", + "0.775": "25", + "0.76": "26", + "0.74": "27", + "0.72": "28", + "0.7": "29", + "0.68": "30", + "0.66": "31", + "0.64": "32", + "0.62": "33", + "0.6": "34", + "0.57": "35", + "0.54": "36", + "0.515": "37", + "0.49": "38", + "0.47": "39", + "0.43": "40", + "0.39": "41", + "0.35": "42", + "0.31": "43", + "0.27": "44", + "0.23": "45", + "0.19": "46", + "0.15": "47", + "0.11": "48"} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..9076a937cba736eba350b8fc69d49a6036284ed8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,56 @@ +asttokens==2.4.1 +audioread==3.0.1 +blinker==1.8.2 +certifi==2024.6.2 +cffi==1.16.0 +charset-normalizer==3.3.2 +click==8.1.7 +contourpy==1.2.1 +cycler==0.12.1 +decorator==5.1.1 +executing==2.0.1 +Flask==3.0.3 +fonttools==4.53.0 +gunicorn==22.0.0 +idna==3.7 +ipython==8.25.0 +itsdangerous==2.2.0 +jedi==0.19.1 +Jinja2==3.1.4 +joblib==1.4.2 +kiwisolver==1.4.5 +lazy_loader==0.4 +librosa==0.10.2.post1 +llvmlite==0.43.0 +MarkupSafe==2.1.5 +matplotlib==3.9.0 +matplotlib-inline==0.1.7 +msgpack==1.0.8 +numba==0.60.0 +numpy==1.26.4 +packaging==24.1 +parso==0.8.4 +pexpect==4.9.0 +pillow==10.3.0 +platformdirs==4.2.2 +pooch==1.8.2 +prompt_toolkit==3.0.47 +ptyprocess==0.7.0 +pure-eval==0.2.2 +pycparser==2.22 +Pygments==2.18.0 +pyparsing==3.1.2 +python-dateutil==2.9.0.post0 +requests==2.32.3 +scikit-learn==1.5.0 +scipy==1.13.1 +six==1.16.0 +soundfile==0.12.1 +soxr==0.3.7 +stack-data==0.6.3 +threadpoolctl==3.5.0 +traitlets==5.14.3 +typing_extensions==4.12.2 +urllib3==2.2.2 +wcwidth==0.2.13 +Werkzeug==3.0.3 diff --git a/static/css/stl.css b/static/css/stl.css new file mode 100644 index 0000000000000000000000000000000000000000..a22827444b3785e73cae0ac9c8761fae934350be --- /dev/null +++ b/static/css/stl.css @@ -0,0 +1,248 @@ +@font-face { + font-family: Minecraft; + src: url("../resources/Minecraft-Regular.otf") +} + +.headercover { + font-family: Minecraft, Verdana, sans-serif; + background-image: url("../images/model.png"); + background-attachment: fixed; + background-repeat: no-repeat; + background-position: center; + padding: 120px 20px 120px; + background-size: cover; + max-height: 50%; +} + +#pagemaintitle { + background-color: rgba(0,0,0,0.6); + color: white; + padding: 15px; + margin-bottom: 0px; +} +#pagesubtitle { + background-color: rgba(60,60,60,0.5); + color: white; + padding: 10px; + margin-top: 0px; +} +.dragDropDisplay { + background: linear-gradient(0deg, rgba(255, 225, 200, 0.4), rgba(255, 225, 200, 0.4)), + url('../images/dragover_bg.png') repeat; + /* background-color: rgba(230,230,230,0.85); */ + z-index: 1000; + font-family: Minecraft, Verdana, sans-serif; +} +.dragDropIcon svg { + color: white; + transform: scale(5); + margin: 4rem auto; + display: block; +} +.dragDropButton { + color: black; + font-size: large; + background-color: rgb(170, 170, 170); + border: solid black 1px; + pointer-events: none; + box-shadow: -3px -3px rgb(120, 120, 120) inset, + 3px 3px rgb(210, 210, 210) inset; /* Right and Bottom shadow */ +} + +#displayImage { + /*Don't make a low-res image appear blurry when enlarging*/ + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + -ms-interpolation-mode: nearest-neighbor; +} + +#footertitle { + font-family: Minecraft; + color: white; +} + +.modal-backdrop.show { + opacity: 0.7; +} +.bg-light { + background-color: rgb(240,240,240) !important; +} +.sizezero { + height: 0px; + width: 0px; +} + +.profile-avatar { + width: 3rem; + height: 3rem; + border-radius: 50%; + margin: 1rem 0.6rem; +} + +kbd { + background-color: #555555; + white-space: nowrap; +} + +/* Don't use bootstrap's .modal as static or it will never close */ +#spinnerModal { + left: 0; + top: 0; + width: 100%; + height: 100%; + position: fixed; + overflow: auto; + z-index: 1051; + background-color: rgb(0,0,0); + background-color: rgba(0,0,0,0.7); +} +#spinnerModalHeading { + z-index: 1061; +} + +#demoCarousel { + max-width: 750px; +} + +.checkbox-scaled input[type=checkbox] { + -ms-transform: scale(1.5); /* IE */ + -moz-transform: scale(1.5); /* FF */ + -webkit-transform: scale(1.5); /* Safari and Chrome */ + -o-transform: scale(1.5); /* Opera */ + transform: scale(1.5); + padding: 10px; +} + +table caption { + caption-side: top; +} + +.delete-X:hover { + color: red; + cursor: pointer; + font-weight: bold; + font-size: 120%; +} + +#survGuidePlaceholderText { + padding-left: 200; + padding-right: 200; +} + +.guide-tableareas { + overflow: scroll !important; + max-height: 1500; +} + +.guide-tableareas td.focused { + -ms-transform: scale(1.5); + -moz-transform: scale(1.5); + -webkit-transform: scale(1.5); + -o-transform: scale(1.5); + transform: scale(1.5); +} + +/* based on Chrome's "user agent stylesheet" for ":focus-visible" */ +.guide-tableareas td.focused { + outline: black auto 1px; +} + +@media (max-width: 768px) { + .checkbox-scaled input[type=checkbox] { + -ms-transform: scale(1.0); /* IE */ + -moz-transform: scale(1.0); /* FF */ + -webkit-transform: scale(1.0); /* Safari and Chrome */ + -o-transform: scale(1.0); /* Opera */ + transform: scale(1.0); + padding: 5px; + } + #colourPaletteTable thead th { + font-size: 90%; + padding: 6px; + } + #colourPaletteTable .colour-insert { + padding: 0; + } + #colourPaletteTable .colour-insert svg { + width: 0.75em; + height: 0.75em; + } + #colourPaletteTable td:nth-child(4) { + font-size: 80%; + padding: 10px 2px; + } + #colourPaletteTable td:nth-child(3) { + font-size: 85%; + max-width: 20%; + word-spacing: 80%; + } + #survGuidePlaceholderText p { + width: 100% !important; + } + #guideTabsContainer nav { + overflow-x: scroll; + } +} + +.spaced-list li { + padding-bottom: 10px; +} + +#light-dark-toggle-label::before { + background-color: black; + top: 0.35em; +} +#light-dark-toggle-label::after { + top: calc(0.35em + 2px); +} + +.text-info.text-info-inverted { + /* bootstrap marked it as important already */ + color: white !important; +} + +.bs-popover-top > .arrow::before, .bs-popover-top > .arrow::after { + border-width: 1.45rem 0.5rem 0; +} +.bs-popover-bottom > .arrow::before, .bs-popover-bottom > .arrow::after { + border-width: 0 0.5rem 1.45rem; +} + +.bs-popover-top > .arrow { + bottom: calc(-1.45rem - 0px); +} +.bs-popover-bottom > .arrow { + top: calc(-1.45rem - 0px); +} + +.bs-popover-top { + margin-bottom: 1.45rem; +} +.bs-popover-bottom { + margin-top: 1.45rem; +} + +.visbox-vis:hover, .visbox-invis:hover { + cursor: pointer; +} +[type="checkbox"].visbox { + display: none; +} +[type="checkbox"].visbox:checked ~ .visbox-invis { + display: none; +} +[type="checkbox"].visbox:checked ~ .visbox-vis { + opacity: 10%; +} +[type="checkbox"].visbox:checked:hover ~ .visbox-vis { + opacity: 80%; +} +[type="checkbox"].visbox:not(:checked) ~ .visbox-vis { + display: none; +} + +.guide-tableareas td.minimized { + opacity: 50%; + transform: scale(0.2); +} \ No newline at end of file diff --git a/static/css/style-dark.css b/static/css/style-dark.css new file mode 100644 index 0000000000000000000000000000000000000000..7db707350cc914e90f382e7ddd49f4622924d7e5 --- /dev/null +++ b/static/css/style-dark.css @@ -0,0 +1,147 @@ + +.dark-theme, +.dark-theme .modal-content, +.dark-theme table { + background-color: #1b1b1b; + color: ghostwhite; +} + +.dark-theme .alert-info { + color: #def; + border-color: #258; + background-color: #0a2a4a; +} +.dark-theme .alert-info .alert-link { + color: white; +} +.dark-theme .alert-success { + color: #d4edda; + border-color: #285; + background-color: #155724; +} +.dark-theme .alert-success .alert-link { + color: lightcyan; +} +.dark-theme .alert-warning { + color: #dca; + border-color: #885; + background-color: #221900; +} +.dark-theme .alert-warning .alert-link { + color: ivory; +} +.dark-theme .alert-danger { + color: #f6e6e7; + border-color: #9a414a; + background-color: #36060b; +} +.dark-theme .alert-danger .alert-link { + color: #ffe6e7; +} + +.dark-theme .bg-light { + background-color: #555 !important; +} +.dark-theme .bg-dark { + background-color: black !important; +} +.dark-theme .text-muted { + color: rgb(180,180,180) !important; +} +.dark-theme .form-control, +.dark-theme .form-control:active, +.dark-theme .table .thead-light th, +.dark-theme .custom-file-label { + background-color: #101010; + border-color: #606060; + color: ghostwhite; +} +.dark-theme .custom-file-label::after, +.dark-theme .nav-tabs .nav-link.active { + background-color: #222; + border-color: #606060; + color: ghostwhite; +} + +.dark-theme .table td, .dark-theme .table th { + border-top: 1px solid #888; +} + +.dark-theme .nav-tabs { + border-bottom: 1px solid lightslategray; +} + +.dark-theme .text-primary { + color: #399fff !important; +} +.dark-theme .text-dark { + color: #f0ffef!important; +} + +.dark-theme a:not(.btn) { + color: darkturquoise; +} + +.dark-theme .custom-control-label::before { + background-color: #222; +} +.dark-theme .border-dark { + border-color: #888 !important; +} +.dark-theme .form-control:disabled, +.dark-theme .custom-file-input:disabled~.custom-file-label, +.dark-theme .custom-control-input:disabled~.custom-control-label::before { + background-color: #444 !important; + color: lightgray !important; +} +.dark-theme .btn-outline-secondary { + color: lightgray; + border-color: lightgray; +} +.dark-theme .btn-outline-danger { + color: rgb(255, 50, 50); + border-color: rgb(255, 50, 50); +} +.dark-theme .btn-outline-danger:hover { + color: white; +} +.dark-theme .page-link { + color: #399fff; + background-color: #606060; +} + +.dark-theme kbd { + background-color: #555555; + white-space: nowrap; +} +.dark-theme code { + color: #ff70b3; +} +.dark-theme pre code { + color: beige; +} +.dark-theme .figure-img, +.dark-theme .carousel-item img { + filter: brightness(85%); +} +.dark-theme .figure-caption { + color: lightgray; +} + +.dark-theme .popover { + color: #212529; +} + +.dark-theme .table-hover tbody tr:hover { + color: beige; + background-color: #555555; +} + +.dark-theme #github-icon { + color: white !important; +} + +.dark-theme .dragDropDisplay { + background: linear-gradient(0deg, rgba(30,30,30,0.6), rgba(30,30,30,0.6)), + url('../images/dragover_bg.png') repeat; +} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..1ed890234b15150850cb2c7bd07999b175062585 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,177 @@ +@font-face { + font-family: Minecraft; + src: url("../resources/Minecraft-Regular.otf") +} + +.headercover { + font-family: Minecraft, Verdana, sans-serif; + background-image: url(../images/model.png); + background-attachment: fixed; + background-repeat: no-repeat; + background-position: center; + padding: 120px 20px 120px; + background-size: cover; + max-height: 50%; +} + +#pagemaintitle { + background-color: rgba(0, 0, 0, 0.6); + color: white; + padding: 15px; + margin-bottom: 0px; +} + +#pagesubtitle { + background-color: rgba(60, 60, 60, 0.5); + color: white; + padding: 10px; + margin-top: 0px; +} + +.minecraft { + font-family: 'Minecraft', sans-serif; +} + +#tabContainer { + background-color: #8B4513; + /* Minecraft dirt color */ + border: 5px solid #D2691E; + /* Minecraft dirt edge color */ +} + +#loadingSpinner { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: rgba(255, 255, 255, 0.8); + padding: 20px; + border-radius: 8px; + text-align: center; + display: none; +} + +.form-check-inline { + display: inline-block; + width: 24%; +} + +.custom-file-label::after { + content: "Browse"; +} + +.btn-primary { + background-color: #00BFFF; + border-color: #00BFFF; +} + +.form-control-sm { + background-color: #D3D3D3; + /* Light grey */ + border-color: #A9A9A9; + /* Dark grey */ +} + +.form-control-range { + background-color: #D3D3D3; +} + +.custom-range::-webkit-slider-thumb { + background: #00BFFF; + /* Light blue */ +} + +.form-check-input { + background-color: #00BFFF; + border-color: #00BFFF; +} + +.custom-file-input:focus~.custom-file-label { + box-shadow: none; + border-color: #A9A9A9; +} + +.form-check-label { + color: white; +} + +input[type="range"] { + appearance: none; + -webkit-appearance: none; + /* margin: auto; */ + position: relative; + overflow: hidden; + height: 20px; + width: 400px; + cursor: pointer; + padding-top: 10px; + border-radius: 0; /* iOS */ + vertical-align: middle; +} + +::-webkit-slider-runnable-track { + background: #ddd; +} + +::-webkit-slider-thumb { + -webkit-appearance: none; + width: 20px; /* 1 */ + height: 40px; + background: #fff; + box-shadow: -400px 0 0 400px dodgerblue; /* 2 */ + border: 2px solid #999; /* 1 */ +} + +::-moz-range-track { + height: 40px; + background: #ddd; +} + +::-moz-range-thumb { + background: #fff; + height: 40px; + width: 20px; /* 1 */ + border: 3px solid #999; /* 1 */ + border-radius: 0 !important; + box-shadow: -200px 0 0 200px dodgerblue; + box-sizing: border-box; +} + +::-ms-fill-lower { + background: dodgerblue; +} + +::-ms-thumb { + background: #fff; + border: 2px solid #999; /* 1 */ + height: 40px; + width: 20px; /* 1 */ + box-sizing: border-box; +} + +::-ms-ticks-after { + display: none; +} + +::-ms-ticks-before { + display: none; +} + +::-ms-track { + background: #ddd; + color: transparent; + height: 40px; + border: none; +} + +::-ms-tooltip { + display: none; +} + + +#simThreshOutput, #simThreshOutputCommand { + display: inline-block; + width: 40px; + text-align: center; + color: white; +} \ No newline at end of file diff --git a/static/images/favicon-16x16.png b/static/images/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..0b0814332fe1bb7ea73da11fe1b81e39e2f101fb --- /dev/null +++ b/static/images/favicon-16x16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3828ec1f62c956ebc1b926d002a450f77fd12fb48d049cff692e1b180a7d459b +size 1755 diff --git a/static/images/favicon-32x32.png b/static/images/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..dbaeed19f4605c00a0ebaf0733cb8dee3b80cc9e --- /dev/null +++ b/static/images/favicon-32x32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b6e6df5c96e335cd4bc2d218844ee52ed4f062292d36f6a4ba7152279fbb44 +size 3098 diff --git a/static/images/model.png b/static/images/model.png new file mode 100644 index 0000000000000000000000000000000000000000..22f3a7296557d264f3c4c95e9571bae7b7cff38f --- /dev/null +++ b/static/images/model.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b74810406014da9c39e776e190771cc6cf0dff0f398dc4f784db096916ed8e13 +size 3831795 diff --git a/static/js/data.js b/static/js/data.js new file mode 100644 index 0000000000000000000000000000000000000000..7c7cbd80f7ac321e901f20ae2e62783cc6fb2ffe --- /dev/null +++ b/static/js/data.js @@ -0,0 +1,26 @@ +/** + * Data of all minecraft blocks used by the music command gennerator + * Most of the data has been determined experimentally with minimal reference to publically available sources. + * Each value of a 'music-note' is in the following format - + * { + * 'name': String + * 'description': String + * 'id': String + * 'structure': String + * } + */ + +const Blocks = new Map([ + ["Guitar",{ + name: "Guitar", + description: "green_wool", + id:'concrete["instrument":"guitar"]', + structure:"" + }], + ["Bass",{ + name:"", + description:"", + id:"", + structure:"" + }] +]) \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000000000000000000000000000000000000..ea1bc5d19282dbd7ab66400e4bae93cf22114dca --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,217 @@ +function float32ToWav(buffer, sampleRate) { + const encodeWAV = (samples, sampleRate) => { + const buffer = new ArrayBuffer(44 + samples.length * 2); + const view = new DataView(buffer); + + const writeString = (view, offset, string) => { + for (let i = 0; i < string.length; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } + }; + + const floatTo16BitPCM = (output, offset, input) => { + for (let i = 0; i < input.length; i++, offset += 2) { + const s = Math.max(-1, Math.min(1, input[i])); + output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); + } + }; + + writeString(view, 0, 'RIFF'); + view.setUint32(4, 32 + samples.length * 2, true); + writeString(view, 8, 'WAVE'); + writeString(view, 12, 'fmt '); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + view.setUint16(22, 1, true); + view.setUint32(24, sampleRate, true); + view.setUint32(28, sampleRate * 2, true); + view.setUint16(32, 2, true); + view.setUint16(34, 16, true); + writeString(view, 36, 'data'); + view.setUint32(40, samples.length * 2, true); + + floatTo16BitPCM(view, 44, samples); + + return buffer; + }; + + const samples = new Float32Array(buffer); + const wavBuffer = encodeWAV(samples, sampleRate); + return new Blob([wavBuffer], { type: 'audio/wav' }); +} + +document.addEventListener("DOMContentLoaded", function () { + // -------------------------------------------------------------- + const simThresh = document.getElementById("simThresh"); + const simThreshOutput = document.getElementById("simThreshOutput"); + + simThresh.addEventListener("input", function () { + simThreshOutput.textContent = simThresh.value; + }); + + const simThreshCommand = document.getElementById("simThreshCommand"); + const simThreshOutputCommand = document.getElementById("simThreshOutputCommand"); + + simThreshCommand.addEventListener("input", function () { + simThreshOutputCommand.textContent = simThreshCommand.value; + }); + + // Update file input label with selected file name + document.querySelector('.custom-file-input').addEventListener('change', function (e) { + const fileName = document.getElementById("audioFile").files[0].name; + const nextSibling = e.target.nextElementSibling; + nextSibling.innerText = fileName; + }); + // -------------------------------------------------------------- + + // -------------------------------------------------------------- + var audioList = document.getElementById('audioList'); + Object.keys(audios).forEach(function (audioId) { + var container = document.createElement('div'); // Create a container div + container.className = 'audio-container mb-3'; // Add some margin for spacing + + var button = document.createElement('button'); + button.type = 'button'; + button.className = 'btn btn-secondary btn-block mt-2'; + button.textContent = audioId; // Use the audioId as the button label + + var audioWrapper = document.createElement('div'); // Wrapper to hold both audio elements + audioWrapper.className = 'd-none flex-column'; // Use Bootstrap's d-none to hide initially and flex-column for vertical layout + + Object.keys(audios[audioId]).forEach(function(key){ + var audioContainer = document.createElement('div'); // Container for original audio + audioContainer.className = 'd-flex align-items-center mb-2'; // Flex layout for label and audio on the same line with some bottom margin + var label = document.createElement('span'); // Label for original audio + label.textContent = key + ": "; + label.className = 'mr-2 small'; // Margin to the right of the label + var audioPlayer = document.createElement('audio'); + audioPlayer.controls = true; + audioPlayer.src = URL.createObjectURL(float32ToWav(new Float32Array(audios[audioId][key][0]), audios[audioId][key][1])); // use the original audio as source + audioContainer.appendChild(label); + audioContainer.appendChild(audioPlayer); + + audioWrapper.appendChild(audioContainer); + }) + + button.addEventListener('click', function () { + // Hide all other audio players and remove highlight from all buttons + document.querySelectorAll('.audio-container').forEach(function (container) { + container.querySelector('div').classList.remove('d-flex'); + container.querySelector('div').classList.add('d-none'); + container.querySelector('button').classList.remove('btn-primary'); + container.querySelector('button').classList.add('btn-secondary'); + }); + + // Show the associated audio player and highlight the button + audioWrapper.classList.remove('d-none'); + audioWrapper.classList.add('d-flex'); + button.classList.remove('btn-secondary'); + button.classList.add('btn-primary'); + + document.getElementById("audioId").value = audioId; + + document.getElementById("resultCommandMusicForm").innerHTML = ""; + document.getElementById("commandsFormSubmitButton").disabled = false; + // // Your logic to play audio or update form fields goes here + // console.log('Original audio:', audioData.original); + // console.log('Processed audio:', audioData.processed); + // console.log('Pickle result:', audioData.pkl); + }); + + container.appendChild(button); + container.appendChild(audioWrapper); + audioList.appendChild(container); + }); + + // -------------------------------------------------------------- +}); + +document.getElementById('processMusicForm').addEventListener('submit', function (event) { + event.preventDefault(); + document.getElementById('resultProcessMusicForm').innerText = ''; + + // Show loading spinner + let loadingSpinner = document.getElementById('loadingSpinner'); + loadingSpinner.style.display = 'block'; + + // Disable submit button + let submitButton = document.querySelector('#processMusicForm button[type="submit"]'); + submitButton.disabled = true; + + let formData = new FormData(this); + + fetch('/api/process-music', { + method: 'POST', + body: formData, + }) + .then(response => response.json()) + .then(data => { + const audioPlayer = document.createElement('audio'); + // audioPlayer.setAttribute("controls", "controls"); + audioPlayer.src = URL.createObjectURL(float32ToWav((new Float32Array(data.processed_music)), data.sample_rate)); + + const resultDiv = document.getElementById('resultProcessMusicForm'); + resultDiv.innerHTML = ''; + resultDiv.appendChild(audioPlayer); + }) + .catch(error => { + document.getElementById('resultProcessMusicForm').innerText = 'Error: ' + error; + }) + .finally(() => { + // Hide loading spinner + loadingSpinner.style.display = 'none'; + + // Re-enable submit button + submitButton.disabled = false; + }); +}); + +document.getElementById('commandMusicForm').addEventListener('submit', function (event) { + event.preventDefault(); + document.getElementById('resultCommandMusicForm').innerText = ''; + + // Show loading spinner + let loadingSpinner = document.getElementById('loadingSpinner'); + loadingSpinner.style.display = 'block'; + + // Disable submit button + let submitButton = document.querySelector('#commandMusicForm button[type="submit"]'); + submitButton.disabled = true; + + let formData = new FormData(this); + + fetch('/api/get-commands', { + method: 'POST', + body: formData, + }) + .then(response => { + if (!response.ok) { + return response.json().then(errorData => { + // Create a new error with the message from the backend + let error = new Error('Network response was not ok'); + error.data = errorData; + throw error; + }); + } + return response.json(); + }) + .then(data => { + let downloadButton = document.createElement('a'); + downloadButton.innerText = formData.get('audioId') + '-' + formData.get('commandsAmplitudeMode') + '.mcfunction'; + downloadButton.href = URL.createObjectURL(new Blob([data.data], { type: 'text/plain' })); + downloadButton.download = formData.get('audioId') + '-' + formData.get('commandsAmplitudeMode') + '.mcfunction'; + document.getElementById('resultCommandMusicForm').appendChild(downloadButton); + downloadButton.click(); + }) + .catch(error => { + let errorMessage = error.data ? error.data.message : error.message; + document.getElementById('resultCommandMusicForm').innerText = 'Error: ' + errorMessage; + }) + .finally(() => { + // Hide loading spinner + loadingSpinner.style.display = 'none'; + + // Re-enable submit button + submitButton.disabled = false; + }); +}); diff --git a/static/resources/Minecraft-Regular.otf b/static/resources/Minecraft-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..54f08ad42ae2c7e9eb4d46c7b621544e32cfc9a2 Binary files /dev/null and b/static/resources/Minecraft-Regular.otf differ diff --git a/static/site.webmanifest b/static/site.webmanifest new file mode 100644 index 0000000000000000000000000000000000000000..1df7181628a41b9eb39aa3a6bb85970411972505 --- /dev/null +++ b/static/site.webmanifest @@ -0,0 +1,20 @@ +{ + "name": "Music Command Generator for Minecraft", + "short_name": "Music Command Generator", + "icons": [ + { + "src": "./images/model.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "./images/model.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#C0C0C0", + "background_color": "#C0C0C0", + "display": "standalone", + "start_url": "/" +} \ No newline at end of file diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000000000000000000000000000000000000..741c8f563a01b3f55a22f70f1b96422aeb40d635 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,121 @@ + + + + mc music command generator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+

About


+

Map Art Maker for Minecraft

+

This site is not approved by or associated in any way with Minecraft or Mojang Studios or Microsoft.

+



  Author

+
+ Originally written by LazyxGhost & i0girish
+ June 2024 - Last Updated +

+

If you have any feedback or suggestions, do let us know. You can write to us on nightterror5678@gmail.com

+
+



  Credits

+

The following third party libraries/services are used by this site :

+
    +
  • Bootstrap CSS 4.5.2 +
  • Minecraft-Regular Font in the page titles by JDGraphics
  • +
+



  Disclaimer

+
+

we can't guarantee that any functionality of this app is correct/accurate at all times.

+

With the release of this patch 1.0, we aim to introduce new features and improvements as and when possible. Minecraft is a huge game with new features constantly being added, and smaller + unofficial projects like this from the community sometimes take a while to catch up. Probably the most critical thing that could change is the addition of new instruemnts of change in functionality, + which seems unlikely or rather unsightable in near future.
Besides this, there may be some glitches in the app/site which are + inadvertently present. We attempt to fix any of these when We notice them. +

+
+
+ +



+ + + + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c6704b3bcb76e95176ca9d3c7d53cfb0c4828002 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,287 @@ + + + + + Minecraft Noteblock Music Generator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

How this app works

+

+ It takes your uploaded audio and converts it into a translable noteblock audio that you can accordingly place to reproduce a similar audio in Minecraft using vanilla blocks! +

+
    +
  • + The placement is automated by writing commands into a + + behaviour pack . These can be readily executed using a few '/function' commands in-game, + through a command block or by a player. +
  • +
  • + The packs are valid for the latest version of Minecraft on supported Bedrock edition devices - Win + 10, Android, iOS, + etc +
  • +
+

+ This web app is + not open source + ! It is not officially associated with + Minecraft or + Mojang. +

+

+ Other technical explanation, and an example add-on for download, can be found + here. +

+ +
+ +
+

Crafting table

+
+
+
+ + +
+
+ +
+ +
+ + +
+
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + + 0.7 +
+ + +
+
+
+

Create the Add-On

+
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + + 0.7 +
+
+ + +
+ +
+
+
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/templates/manual.html b/templates/manual.html new file mode 100644 index 0000000000000000000000000000000000000000..92b3af993511992dea625de989d1db9153c1b93d --- /dev/null +++ b/templates/manual.html @@ -0,0 +1,192 @@ + + + + mc music command generator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+

Manual


+
+
This isn't the main page of this application !
+ + +
+

Browser Compatibility

+

It is recommended to use this app on a desktop/laptop instead of on mobile.

+

The app has been tested on recent versions of Google Chrome, Safari and Microsoft Edge. + Internet Explorer is not fully compatible. Since this is a client-side app (It is run entirely by your browser, + no data is sent back the server for processing !), some essential functions may not + work if your browser hasn't been updated for a long while, or your device places many + restrictions on it.

+

This site does not use any cookies.

+

PWA : This app can work offline, using the features of Progressive Web Apps + +  . Click the 'Try Offline' button in the footer to cache the resources + to do so (this will only take around 3 MB of space). If your browser supports these features, it will show you an option + to install this website as a native application on your system ! On mobile, you can equivalently save it to the Home Screen.
+ Note : On desktop, PWAs may not be fully supported in browsers other than recent versions of Chrome, Edge & Safari. + Even without the installable app, you will be able to use the website without an internet connection. + You can clear the cached website from your browser settings if you wish to do so. +

+


Miscellaneous notes

+
    +
  • When you create the add-on, commands to build all the Midi-machine which plays the music in-game that was uploaded and processed + will be written into it. Every floor of the machine plays 2 seconds of song leading to around 65*50 blocks being used in x-z axis (which is a + lot !), Given your initial coordinates, make sure that one y level below is ready to be replaced. Every noteblock placement is accompanied with corresponding music block + and per unit time has a full range of redstone wire as per the Minecraft audible range. they are + regular commands like /setblock, More instructions on running the functions are given below. +
    + Read more about functions in Minecraft.

  • +
  • The converted audio heard after you click the 'Process Audio' button may vary in quality depending on the song chosen + (especially in the "noise"generated due to limitations in minecraft noteblock sounds being 8-bit sounds) This is unavoidable. + You can change your experience by selecting/deselecting some instruments which will change the overall quality of the song.

  • +
  • + Make sure to keep enough room in y aixs for the midi machine to fit completely as even 20 sec song wil take 10 floors wit heach floor being + 4 blocks high. Do not place two midi machine in an overlapping manner as that will lead to breakage in mechanism. +

  • +
  • The fill commands to clear the map volume of existing blocks (default, unless you checked the "Replace only air blocks" + option) may fail in case your simulation distance is too low. In this case, existing terrain above and below the 3D map blocks + will remain, possibly hiding the blocks that were placed there.

  • +
+



In Minecraft

+
    +
  1. Import and apply the behaviour pack to your world(s).
    Note : Cheats & + Operator permissions must be enabled to use functions. If you want to earn achievements in that world or keep cheats + off, you'll have to place the blocks manually (You can look at the Survival Guide for some help).
  2. +
  3. Go to the location where the midi machine is to be built, and stande near the staring point but dont stand inside the buildable region.
    +
  4. +
  5. The syntax of the function command is /function machine_name.
    + For example, if your machine name was onesummerday (make sure to keep names with no spaces and no capital names), you would need to run + the commands
    /function onesummerday

    + Note : You may need to restart Minecraft and then run the /reload command first, + in case they don't appear in the commands screen or minecraft hasn't recognised the functions
     
  6. +
  7. You're Done !

    Once you've constructed the machine, + there will also a railcart system built in order to enjoy the song lesiurely.
    All you need to do is sit in the cart and press the button. +
    +

  8. +
+

+



Example behaviour pack

+

This is the file structure of an example behaviour pack generated by this app. It contains 2 + images - one with the function name "cliff" of size 2x1, and another named "bastion" of + size 3x2. You can run the commands as described above to build these images.

+

+            behaviour pack.mcpack
+             │
+             ├─ manifest.json
+             ├─ pack_icon.png
+             ├─ structures/
+             │    └─ mapart/
+             │         └   ...
+             └─ functions/
+                  ├─ cliff/
+                  │    ├─ 1.mcfunction
+                  │    ├─ 2.mcfunction
+                  │    ├─ 3.mcfunction 
+                  │    └─ 4.mcfunction
+                  └─ bastion/
+                       ├─ 1.mcfunction
+                       ├─ 2.mcfunction
+                       ├     ...
+                       └─ 12.mcfunction
+            

+
+
  • The manifest specifies : +
    • The minimum game version - 1.20.0 (of Minecraft)
    • +
    • Behaviour pack version - 1.0.0 is used for each pack by default. (Only this sample pack has version 4.5.0)
    • +
    • The name and description that you entered, will be displayed for the add-on in game.
    • +
    • 2 version-4 + UUIDs to uniquely identify the pack, generated randomly using the JS web Crypto API.
    + You can always edit the icon or manifest if you prefer to make any changes later on.

+

The pack contains some structures (with block data) of a few blocks that may decay or need support (non-solid blocks) + etc - these structures are used instead of the setblock command to overcome those limitations. Every pack will contain + these even if they are not used in any image(s).

+

To view/edit/replace the files inside, just rename the .mcpack extension to .zip + and extract all contents using any Compressed (ZIP) folder utility.

+


+ +
+ + + + diff --git a/test.py b/test.py new file mode 100644 index 0000000000000000000000000000000000000000..f932bd6b18075334ad328da229360ef24e6e6b64 --- /dev/null +++ b/test.py @@ -0,0 +1,33 @@ +import numpy as np +import librosa +import audioread +import soundfile as sf + +def read_audio(path): + try: + if path[-4:] == '.ogg': + y, sr_native = sf.read(path) + else: + buf = [] + with audioread.audio_open(path) as input_file: + sr_native = input_file.samplerate + n_channels = input_file.channels + for frame in input_file: + frame = (1.0 / float(1 << 15)) * np.frombuffer(frame, f" 1: + y = y.reshape((-1, n_channels)).T + y = np.mean(y, axis=tuple(range(y.ndim - 1))) + y = librosa.resample(y, orig_sr=sr_native, target_sr=22050, res_type="soxr_hq") + return y, 22050 + except Exception as e: + print(f"Error reading audio file: {e}") + return None, None + +# print(audioread.audio_open('MusicAnalyzer/Instruments/banjo.ogg')) +print(read_audio('/Users/personal/Desktop/minecraft_noteblock_music_generator/MusicAnalyzer/Instruments/banjo.ogg')) +print(read_audio('/Users/personal/Desktop/minecraft_noteblock_music_generator/ExtraSounds/gettysburg.wav')) +print(read_audio('/Users/personal/Desktop/minecraft_noteblock_music_generator/ExtraSounds/KneeSocks.m4a')) +print(read_audio('/Users/personal/Desktop/minecraft_noteblock_music_generator/Audios/DilKyuYeMera/original.mp3')) +print(read_audio("Audios/RiverFlowsInYou/processed-Mean.mp3")) diff --git a/utils.py b/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..327bc07a0c277ec34ebbd08f00cc74d3f4bade5b --- /dev/null +++ b/utils.py @@ -0,0 +1,170 @@ +from MusicAnalyzer.musicAnalyzer import preProcess +from MusicAnalyzer.commandGenerator import generateCommands +import os +import multiprocessing as mp +import pickle as pkl +import numpy as np +import soundfile as sf +import librosa + +def preProcess_wrapper(params): + ( + id, + audio_part, + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + audio_id_dir_path, + amplitudeMode, + ) = params + + return id, preProcess( + audio_part, + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + audio_id_dir_path, + amplitudeMode, + ) + + +def combine_parallel_processing_results(results): + out = [] + ct = 0 + for data in results: + infos = data[1] + for info in infos: + out.append((ct, info[1])) + ct += 100 + return out + + +def combine_results_sounds_files(sounds_folder_path, results_file_name): + combined_audio = np.array([]) + audio_files_name = [] + for file_name in os.listdir(sounds_folder_path): + if file_name[: len(results_file_name)] == results_file_name: + audio_files_name.append(file_name) + + sr = 0 + audio_files_name = sorted(audio_files_name) + for audio_file_name in audio_files_name: + audio_file_path = os.path.join(sounds_folder_path, audio_file_name) + audio, sr = librosa.load(audio_file_path, sr=None) + combined_audio = np.concatenate((combined_audio, audio)) + os.remove(audio_file_path) + results_file_path = os.path.join(sounds_folder_path, results_file_name + ".mp3") + sf.write(results_file_path, combined_audio, sr, format="MP3") + return combined_audio, sr + + +def call_file_processing_logic_parallely( + mainAudioValues, + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + audio_id_dir_path, + amplitudeMode, + parallel_processes_count +): + split_size = len(mainAudioValues) // parallel_processes_count + audio_parts = [ + mainAudioValues[i * split_size : (i + 1) * split_size] + for i in range(parallel_processes_count) + ] + audio_parts[-1] = mainAudioValues[(parallel_processes_count - 1) * split_size :] + params_list = [ + ( + i, + audio_parts[i], + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + os.path.join(audio_id_dir_path, f"processed-{amplitudeMode}-{i}.mp3"), + amplitudeMode, + ) + for i in range(parallel_processes_count) + ] + with mp.Pool(processes=parallel_processes_count) as pool: + results = pool.map(preProcess_wrapper, params_list) + results = combine_parallel_processing_results(results) + + if os.path.exists( + os.path.join(audio_id_dir_path, f"processed-{amplitudeMode}.mp3") + ): + os.remove(os.path.join(audio_id_dir_path, f"processed-{amplitudeMode}.mp3")) + processed_audio, sr = combine_results_sounds_files( + audio_id_dir_path, f"processed-{amplitudeMode}" + ) + + with open( + os.path.join(audio_id_dir_path, f"result-{amplitudeMode}.pkl"), "wb" + ) as f: + pkl.dump(results, f) + return processed_audio, sr + + +def convert_to_serializable(obj): + if isinstance(obj, dict): + return {k: convert_to_serializable(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [convert_to_serializable(i) for i in obj] + elif isinstance(obj, tuple): + return tuple(convert_to_serializable(i) for i in obj) + elif isinstance(obj, np.ndarray): + return list(convert_to_serializable(i) for i in obj) + elif isinstance(obj, np.float32): + return float(obj) + else: + return obj + + +# # def create_zip_from_audios(sounds_folder_path): +# # import io +# # import zipfile +# # zip_buffer = io.BytesIO() +# # with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf: +# # for filename in os.listdir(sounds_folder_path): +# # audio_id_dir_path = os.path.join(sounds_folder_path, filename) +# # zipf.write(audio_id_dir_path, os.path.basename(audio_id_dir_path)) + +# # zip_buffer.seek(0) +# # return zip_buffer + + +def call_command_generator( + data, + music_box_dict, + amplitude_dict, + hearable_range, + one_hundred_milli_horizontal_gap, + starting_coordinates, + one_floor_vertical_gap, + instant_repeater_zs, + pitch_mapping_shift, + sim_thresh, +): + return generateCommands( + data, + music_box_dict, + amplitude_dict, + hearable_range, + one_hundred_milli_horizontal_gap, + starting_coordinates, + one_floor_vertical_gap, + instant_repeater_zs, + pitch_mapping_shift, + sim_thresh, + ) diff --git a/views.py b/views.py new file mode 100644 index 0000000000000000000000000000000000000000..f67e6ea5e4155317f1aee75465d4a79269b52427 --- /dev/null +++ b/views.py @@ -0,0 +1,139 @@ +from flask import jsonify +import utils +import librosa +import os +import pickle as pkl +import traceback + + +def index_view(script_dir): + try: + audios_dir_path = os.path.join(script_dir, "Audios") + audios = {} + for _ in os.listdir(audios_dir_path): + # If condition to prevent .DS_Store error on listdir, since .DS_Store is a not a directory + if os.path.isdir(os.path.join(audios_dir_path, _)): + audio_id = _ + audios[audio_id] = {} + audio_id_dir_path = os.path.join(audios_dir_path, audio_id) + for file_name in os.listdir(audio_id_dir_path): + if '.pkl' not in file_name: + audios[audio_id][file_name.split(".")[0]] = utils.convert_to_serializable(librosa.load(os.path.join(audio_id_dir_path, file_name))) + return audios + except Exception as e: + print(e) + print(f"An error occurred: {e}") + traceback.print_exc() + return jsonify({"message": "Error occurred, will solve it soon"}), 500 + +def process_music_view( + files, + allowed_extensions, + audios_dir_path, + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + amplitudeMode, + parallel_processes_count +): + try: + if "audioFile" not in files: + return jsonify({"error": "No file part"}), 400 + + uploadedFile = files["audioFile"] + if uploadedFile.filename == "": + return jsonify({"error": "No selected file"}), 400 + + allowed = ( + "." in uploadedFile.filename + and uploadedFile.filename.rsplit(".", 1)[1].lower() in allowed_extensions + ) + if uploadedFile and allowed: + audioId = uploadedFile.filename.split('.')[0] + if not os.path.exists(os.path.join(audios_dir_path, audioId)): + os.mkdir(os.path.join(audios_dir_path, audioId)) + audioExtension = uploadedFile.filename.split('.')[1] + # if audioId in os.listdir(audios_dir_path): + # return jsonify({"error": "Audio with same id already exists"}), 400 + + upload_file_path = os.path.join(os.path.join(audios_dir_path, audioId), f"original.{audioExtension}") + uploadedFile.save(upload_file_path) + + mainAudioValues = librosa.load(upload_file_path)[0] + processed_audio, sr = utils.call_file_processing_logic_parallely( + mainAudioValues, + sr, + instruments_dict, + scaling_dict, + initialBestMatchesLength, + simThresh, + binLength, + os.path.join(audios_dir_path, audioId), + amplitudeMode, + parallel_processes_count + ) + + return jsonify({"message": "File preprocessed successfully", "processed_music": list(processed_audio), "sample_rate": sr}), 201 + else: + return jsonify({"error": "File type not allowed"}), 400 + except Exception as e: + print(e) + print(f"An error occurred: {e}") + traceback.print_exc() + return jsonify({"message": "Error occurred, will solve it soon"}), 500 + + +# def get_processed_music_view(files_folder_path): +# results_folder_path = os.path.join(files_folder_path, "Results") +# sounds_folder_path = os.path.join(results_folder_path, "sounds") + +# zip_buffer = utils.create_zip_from_audios(sounds_folder_path) +# return send_file( +# zip_buffer, +# mimetype="application/zip", +# as_attachment=True, +# download_name="audio_files.zip", +# ) + + +def get_commands_view( + pkl_file_path: str, + starting_coordinates: list[int], + music_box_dict, + amplitude_dict, + pitch_mapping_shift, + sim_thresh, + instant_repeater_zs, + hearable_range, + one_floor_vertical_gap, + one_hundred_milli_horizontal_gap, +): + try: + if not os.path.exists(pkl_file_path): + return jsonify({"message": "AudioId hasn't been processed for selected amplitude mode"}), 404 + + with open(pkl_file_path, "rb") as f: + data = pkl.load(f) + + results = utils.call_command_generator( + data, + music_box_dict, + amplitude_dict, + hearable_range, + one_hundred_milli_horizontal_gap, + starting_coordinates, + one_floor_vertical_gap, + instant_repeater_zs, + pitch_mapping_shift, + sim_thresh, + ) + return jsonify({"data": results}), 201 + except Exception as e: + print(e) + print(f"An error occurred: {e}") + traceback.print_exc() + return jsonify({"message": "Error occurred, will solve it soon"}), 500 + \ No newline at end of file