From 580ee2813875661acbc685d735e08f5ecc1607bf Mon Sep 17 00:00:00 2001 From: Amirreza Jabbari <160654459+Amirreza-Jabbari@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:09:39 +0330 Subject: [PATCH] init commit --- README.md | 81 ++++- db.sqlite3 | Bin 0 -> 151552 bytes manage.py | 22 ++ mastering/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 181 bytes mastering/__pycache__/admin.cpython-312.pyc | Bin 0 -> 225 bytes .../advanced_vocal_processor.cpython-312.pyc | Bin 0 -> 11317 bytes mastering/__pycache__/apps.cpython-312.pyc | Bin 0 -> 493 bytes mastering/__pycache__/forms.cpython-312.pyc | Bin 0 -> 830 bytes mastering/__pycache__/models.cpython-312.pyc | Bin 0 -> 1110 bytes .../__pycache__/processors.cpython-312.pyc | Bin 0 -> 15309 bytes mastering/__pycache__/tasks.cpython-312.pyc | Bin 0 -> 1790 bytes mastering/__pycache__/urls.cpython-312.pyc | Bin 0 -> 594 bytes mastering/__pycache__/views.cpython-312.pyc | Bin 0 -> 5272 bytes mastering/admin.py | 3 + mastering/apps.py | 6 + mastering/forms.py | 10 + mastering/migrations/0001_initial.py | 61 ++++ ...ocalmastering_delete_vocalprocessingjob.py | 37 +++ .../0003_vocalmastering_reference_audio.py | 18 + ...4_remove_vocalmastering_reference_audio.py | 17 + mastering/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2073 bytes ..._delete_vocalprocessingjob.cpython-312.pyc | Bin 0 -> 1332 bytes ...lmastering_reference_audio.cpython-312.pyc | Bin 0 -> 855 bytes ...lmastering_reference_audio.cpython-312.pyc | Bin 0 -> 714 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 192 bytes mastering/models.py | 11 + mastering/processors.py | 309 ++++++++++++++++++ mastering/tasks.py | 29 ++ mastering/tests.py | 3 + mastering/urls.py | 8 + mastering/views.py | 85 +++++ requirements.txt | 10 + templates/base.html | 20 ++ templates/mastering/job_status.html | 37 +++ templates/mastering/result.html | 67 ++++ templates/mastering/upload.html | 10 + vercel.json | 15 + vocal_mastering/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 187 bytes .../__pycache__/settings.cpython-312.pyc | Bin 0 -> 2382 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 753 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 691 bytes vocal_mastering/asgi.py | 7 + vocal_mastering/settings.py | 116 +++++++ vocal_mastering/urls.py | 12 + vocal_mastering/wsgi.py | 9 + 48 files changed, 1001 insertions(+), 2 deletions(-) create mode 100644 db.sqlite3 create mode 100644 manage.py create mode 100644 mastering/__init__.py create mode 100644 mastering/__pycache__/__init__.cpython-312.pyc create mode 100644 mastering/__pycache__/admin.cpython-312.pyc create mode 100644 mastering/__pycache__/advanced_vocal_processor.cpython-312.pyc create mode 100644 mastering/__pycache__/apps.cpython-312.pyc create mode 100644 mastering/__pycache__/forms.cpython-312.pyc create mode 100644 mastering/__pycache__/models.cpython-312.pyc create mode 100644 mastering/__pycache__/processors.cpython-312.pyc create mode 100644 mastering/__pycache__/tasks.cpython-312.pyc create mode 100644 mastering/__pycache__/urls.cpython-312.pyc create mode 100644 mastering/__pycache__/views.cpython-312.pyc create mode 100644 mastering/admin.py create mode 100644 mastering/apps.py create mode 100644 mastering/forms.py create mode 100644 mastering/migrations/0001_initial.py create mode 100644 mastering/migrations/0002_vocalmastering_delete_vocalprocessingjob.py create mode 100644 mastering/migrations/0003_vocalmastering_reference_audio.py create mode 100644 mastering/migrations/0004_remove_vocalmastering_reference_audio.py create mode 100644 mastering/migrations/__init__.py create mode 100644 mastering/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 mastering/migrations/__pycache__/0002_vocalmastering_delete_vocalprocessingjob.cpython-312.pyc create mode 100644 mastering/migrations/__pycache__/0003_vocalmastering_reference_audio.cpython-312.pyc create mode 100644 mastering/migrations/__pycache__/0004_remove_vocalmastering_reference_audio.cpython-312.pyc create mode 100644 mastering/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 mastering/models.py create mode 100644 mastering/processors.py create mode 100644 mastering/tasks.py create mode 100644 mastering/tests.py create mode 100644 mastering/urls.py create mode 100644 mastering/views.py create mode 100644 requirements.txt create mode 100644 templates/base.html create mode 100644 templates/mastering/job_status.html create mode 100644 templates/mastering/result.html create mode 100644 templates/mastering/upload.html create mode 100644 vercel.json create mode 100644 vocal_mastering/__init__.py create mode 100644 vocal_mastering/__pycache__/__init__.cpython-312.pyc create mode 100644 vocal_mastering/__pycache__/settings.cpython-312.pyc create mode 100644 vocal_mastering/__pycache__/urls.cpython-312.pyc create mode 100644 vocal_mastering/__pycache__/wsgi.cpython-312.pyc create mode 100644 vocal_mastering/asgi.py create mode 100644 vocal_mastering/settings.py create mode 100644 vocal_mastering/urls.py create mode 100644 vocal_mastering/wsgi.py diff --git a/README.md b/README.md index 02e7092..3d1cc9f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,79 @@ -# vocal-mastering -The Vocal Mastering Application is a Django-based web application designed to facilitate the uploading and processing of vocal audio files. It utilizes advanced audio processing techniques to enhance the quality of vocal recordings, making it suitable for musicians, producers, and audio engineers. + +--- + +# Vocal Mastering Application + +## Overview +The Vocal Mastering Application is a Django-based web application designed to facilitate the uploading and processing of vocal audio files. It utilizes advanced audio processing techniques to enhance the quality of vocal recordings, making it suitable for musicians, producers, and audio engineers. + +## Features +- **File Upload:** Users can upload vocal audio files in various formats (WAV, MP3, M4A). +- **Asynchronous Processing:** Audio processing is handled in the background using Celery, allowing users to continue using the application while their files are being processed. +- **Advanced Audio Processing:** The application employs a range of audio processing techniques, including noise reduction, dynamic compression, equalization, and loudness normalization. +- **Job Status Tracking:** Users can check the status of their audio processing jobs and download the mastered audio once completed. + +## Technologies Used +- **Django:** A high-level Python web framework for building web applications. +- **Celery:** An asynchronous task queue/job queue based on distributed message passing. +- **Librosa:** A Python package for music and audio analysis. +- **NumPy:** A library for numerical computations in Python. +- **SoundFile:** A library for reading and writing sound files. +- **Pyloudnorm:** A library for loudness normalization. + +## Installation +To set up the Vocal Mastering Application locally, follow these steps: + +### Clone the Repository: +```bash +git clone https://github.com/yourusername/vocal-mastering.git +cd vocal-mastering +``` + +### Create a Virtual Environment: +```bash +python -m venv venv +source venv/bin/activate # On Windows use `venv\Scripts\activate` +``` + +### Install Dependencies: +```bash +pip install -r requirements.txt +``` + +### Set Up the Database: +```bash +python manage.py migrate +``` + +### Run the Development Server: +```bash +python manage.py runserver +``` + +### Access the Application: +Open your web browser and navigate to [http://127.0.0.1:8000/](http://127.0.0.1:8000/). + +## Usage +1. **Upload Vocal:** Navigate to the upload page and select an audio file to upload. +2. **Processing:** After uploading, the application will process the audio file in the background. You will be redirected to a job status page. +3. **Download Mastered Audio:** Once processing is complete, you can download the mastered audio file. + +## Code Structure +The application is organized into several key components: +- `forms.py`: Contains the form for uploading audio files. +- `models.py`: Defines the data models for storing audio files and processing jobs. +- `processors.py`: Implements the audio processing logic. +- `tasks.py`: Contains the Celery tasks for asynchronous processing. +- `views.py`: Handles the web requests and responses. +- `urls.py`: Defines the URL routing for the application. + +## Contributing +Contributions are welcome! If you have suggestions for improvements or new features, please open an issue or submit a pull request. + +## License +This project is licensed under the MIT License. See the LICENSE file for details. + +## Sample Screenshot +[![result.png](https://i.postimg.cc/3NDJPFcs/result.png)](https://postimg.cc/sGsrW7ym) + +--- \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..2bda2b1d79bb02b1b2b1f8031a01466ea405aef7 GIT binary patch literal 151552 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 tzqam= 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 YKpPl=xERFvz|6?Vc#lD$hz-aA0PndxX8-^I literal 0 HcmV?d00001 diff --git a/mastering/__pycache__/advanced_vocal_processor.cpython-312.pyc b/mastering/__pycache__/advanced_vocal_processor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c09a3325ef23c1c608516bb2506272b86a88717f GIT binary patch literal 11317 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 VeWI}{0DP9cit{BN~DMVtTt literal 0 HcmV?d00001 diff --git a/mastering/__pycache__/apps.cpython-312.pyc b/mastering/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8619bf4ab4306e47298c2518b5931771337533b7 GIT binary patch literal 493 zcmYL_ze~eF6vy8ssr09cNX0>@i;yMgCQ^iIQ5-C8LI@mZ?;29_%S$5EP3Rxs*2RB7 z{0saWTpUZl$;nNqE}gtf(t5*tpLg#E-}l}cjVg%uTE4cQg#VPm;*|=R?FpCw1&Uno zkcki|a1K-%163J%QS&-e8EJayO9nbhE1fVr4g7&)_w#gaQ4!2G1x$c}i71!~MW#v> zQyZzemabirC}!O8?R>!f4jmHT4h(u@cmZW@WSls5Cy9eIhq*MZc#Ha;UP{Xt`@~}y zr&WwadE!d0V|<$sx5#WzHXw-`VlK6|M@Jm)rSz$9!Fc^0JY<>bHsz`heY^6$NcQI=>)X*7XM+&w@k% literal 0 HcmV?d00001 diff --git a/mastering/__pycache__/forms.cpython-312.pyc b/mastering/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a08d341ea465d363e8b0bdf042e3bbc47e53cb2 GIT binary patch literal 830 zcmZ8fy^9k;6rb6ze30b)I6pu+!ddMJQNcbk=yLMrF&T<(mo`0elQym=qLH(#pNGE%nv)eBsV&<}8y zW}lFGOOYv}h~fyv*uxl6gPM^Mo1Tf0fgT}hO%Syi9u$|C&%QHLbI%?-Zs7B+Zxzlc29}XtYk8d0mkyV#vc3c}89qtFp$-&=HBSy_m^;@Ebb#rl5*8y%Jr zS>qxM!h}RV8PSkShxGewn7u0G?yW7*fMl6SyKYGqeLo>F^L<(N{g~5H1ia$=&qpNE zCjwfRwmM{ybap}((Nuu6veXJA)=q|_Ocq{-G+QrV%Q^rnIdIykqnYad+L z+&_O~*ZE$#zQ_02d;X4pdiHs|f4BW@($(s@E*~dH>w)g102_b+0vRymtuR;1-s1gB9xd8US_l%ej;t`G0`a9Z88fJ!2=1v3 zmoZOUX1K8`xN+<{7HU{e5hkS4E6z_+5mFAKn-xL~5u0D&M?O81Qj#rtBAn zfwtfQD};WAY?lnp?2o)))jhsxv%yGAv)kFb#1fwX8QH?=Iy<6qkECjt}LmP zPkIrA8n{jo@}dZdWpkOtafBYbm<2yvcEr;c7iWr7CS}A^UDqU@CgnI!`kzxKqrqp= z;%3!sV1}?uOv_82Or!Ts6tP1W%zBR=t#B$71Yw7#gsZoJ4~vV-RpfHheW40qKN4aM z9O)y^Gl%-fzCN-^c8c$3-_7o(5A^$;$)j{`ed#cr+fV0q^n>(7r!=UEeZ0O)4$|{N zo64mhJeFi{g|=b4C(niBx7N z+2T^(B1i$I-QG&L10-$s&h&23(6w<<&=jk^_PTxmivri;IH4BOPE^!~2H3jj6>bvX zitPcp@B0sDIF#wAdwnwjtM`@a9j|4~?2KtcK6fAsd~U$j%y-{FZ?*i7ZC zDX1(`JjK%!)UbM_hfU<18D_|l9p=cooY@0g`qEYI*{sD)O{QgAfnpMaWG5+adcXjE!3 zDa^=7L}7zK18OgoW`>}?nt}60N}z^mo*FjUDE!A4^YnW(EZOuPHO%r1P>yGTnsEWJ zbTd#ZZvkrKtw0NS8&Er60Mx`04j8+5*40v?)LfeM zixEM*8V>j;&iN&%!m=-j;ea4XVKJb+T3j=(n1TOawEF(J$k5xQJfLKI4VSZ6X9t-1mi1q$v-(YA^1dpL{L2ZY{)+u48U1vRPaT{L_r!0 zPw*|Q!b-x#h-in=MF)~XAQCRk3*|Rg2aa5u62qe+^bU=Bp*k)EBGR>iaTq9ktz&kd8*bz#M=7Cin znC@dAD06g#Q)?7(af})_YiHSG6k6QRj5=q65vZ7{f|_FtGg-5(wOWkP`ta-xSczzl z;71^Z=d^n?6~PZe%6O4h%N~JIUa~6Q%v&BR+VyecnyC;hYD zP&nchg(+ZQAr$fY1CeR}1YCqxZC)Z<0*{<}GZ-232K>{JU^vv4nc?Y)lP#P=&nlK# zAL*f(BvB~@&Ybe`LO>M!lAshw5kGeFi?iCoejfFx%Mb;AI)FF5f+&W?BiND*G)ra;^ya9>m$q@$3T)HZl4AWY#fEoKqFDZGbiJ{VSt$fA7w zG{`L6RUGQM`tVAjItcGm8`FY>yogfj^@>g$#;LZ~R~c>FMz*%J_rM_1p929AGgaEk zzbGkPJhgCYX)w8SgJF!?Ux^QGRC*J&iMCW_NBk@_ zaTWhy=KY!ZiL|pm!GGp#-E{3tm5!{rM&Vv{?Va<>=QoNy8)Xd}ZqK^AUUt`S)YQQc zU)#;qMe%cA7h|JS%cm0ORt~PZ(^bdg=bpN~DTnu8Hl1Z(QYKsZrejB{WKXg;c_3BX zzUJ7!;V%2bv;X>>gIm9mYTyCh?JMc?P>(&fz9YXZ0Mg)iRk_3 zEHQJ;bYlSefb9Z{VUAhLVg4wXcHYWcV@wp(3!dS)JV(x8#F!;!J%;Pll@BO6)U{hm zjE!+oxR#^rY%#EqIH%ZdSZ6*pWii~>l}dJsm`!`?m_jmYYki4gvPX7m=e#{;e~DtU-?kIQ1V@8natsCk>6>r9d4}Ka*WWGquc!W3^t}_K zKU~K0vlGus;PU7{kNsWzPygkMo@a=Yo}sD-v+17{?$HW68RmtkyFWaMDoHZx2D?~| zExC#CXvcoVGU>k+oSdFi82@!iv4z1%`zC@n1hEzxscOR-2#03E6ElLs;%%`3@6-Wl zaS%NrHsW|T-Fhn=Q51^VABp$_Hx!E~Ob{JrB`U(lUsu>sKj>Mf|9W`FVD=UJ&0vTR z-}FhrsGt-|li_e=OyGUkSZskwB;)|1Su#XbFm4S-1hS7C?c7GmE+WOzW&kh-QiD7KQ(`1UYY)r!qrH+ zaUfkaD0>Fur#Ia@7mqC*OK@p-Q+(iQO;fU5uIY@Q-)Lw`o>{d%td(E6DDSd^I5N;ZvhQc~4Mo7>^Ik*UFAMP)(IJOZ<9Gn_Sbj(zJS5?(CCm`qLEy z@z*w-6-yU?|9hL>T_4%++LHsH?)qu-C(WyS(w!&&vQqB6oO;W@=Dog9_|)4lU-;Br zwsc%}?@l>(=jyBGB%P2_#e3EqdtcO7{jPpD^~7!K?_!=*Ir}@9CwpkDcW^*+^c89m zqOX1@qpz}(mDMJ5rigCj$Dza4G7fUIwL}mqk&+#U2OK({NAIAFYCs);P6jMd)Hn*r zmSVV@Pp{}OXcSzAAJagYAJe+*vk|mxq<~IijoN2Cj~>aR8#se*03G%c-H;!&a6{sL zn7f62#>3&+-@|i0i@Sk%hw!S1TIbgXNcrl7dZsFlXD6a1gYN(`2}X%2*YSc~#5b=h z1`D_?Bckw*!b}JuRRfp-F$m(6hyo(wmsVInB@+fspdtoU6IfvYAMcO&6%NOd6xWm> zBD8>seSv9lMo@)$D^9Qq>e7U5!ME1%JS;`}J`faG3m9Ne#nPew7zOzQh;VY@-os~53pLuK6pV!3IwxpQ2MW?|XuHu&G0qNB`7Pt=(hK7m-%dVBhqp7T&wmNtE0IBAz>47v|Z`vWh#7Wk=$2^3cl6T3OFLw^`r#QTN^Mhg?69 za(Or0^$GuH?&i&^nmZ?#PbLnf_8eWkm}))oaD1)mV(Rs4YnQI3-gqm0=~}Al?S;WF zDy#2wEq5gvQmsc)Ek{;+Q@eW}4zE>SNL{?P_WG67)wj~GUrSZKJwLGNDPO#`a4S)g zm`)U?J#8sh8why(Y_9mf4Cpt&!@k?s)6Y;(Xc{SF>u+M7IGp`;%#%tQ>vbH^{7T?I zY336~QA@sfKt4m6HIKmZT4yS6Z{4Ko&5XjRgLh(P^N8~nGYczLOJ0Dki{c!3@1TfG zH?D#50)=#=0$@Azy=A&Z6dKP)0X_%dy%6B94VwQ6bn~k>gXl)CA%kvqQ7wz5# zA-}xq4VYf~5C{sj742^M;>^O#;&&Imn>dvAv}6tN`GxZz_9wZYyt+cK46Gjh0XC;s-KCQwQk%JU?56{pdw=g0Oq&P+( zmMr)H@{UHvl!7scy{UC#w_8l&tI$dn`SQyUu9kvmsHHLg6o&OQgY8f(6T$0ZSn?~D z5y2ms76pY7CnZ$@oWqG2j4(1_z0rze*ju&Y)`?(9n(_w(gY4U~He3wi^y5e-k(|Jx ziqJ9kO;4$jI9_q!a=>3EZnkRA5x`~axfYJw5z&_K%Pc>-e;ZBkLXVp;tn@#DpaR%w zD}P#98$Y{I-k97Wm$$}W-7GF&Y+qvC}*-Vc5) zK=kf~?xjnK10Nl}d-x~a$-u{>_eSprQ@dVSZB4re;{(vB#(U?@Zi>D}IfUMKaakVfjX^>#9k%ACFXm`BYt*7tEhbGQ-T60uyGCz*}G{gi>y-y8fLj0K{@n<8{%mUavv74Ny$ZO?tVW%+`l*eva40el)SzcndEVset z!KM`)4R*^s^bB`jN$^K3h1zWHD}8<8k)w0KLLiOWzEr=Bj98^sGs_``iH2x ziD;w3QVk7HZztXWSwXBdu!eOsGzex1W9mkPm;)vY17Sy*c?n}Ow;95ia5OL`J0$Ld zk%=XUmYw#Cli?8hLbn1zh#TtYh$ZZ&un_bZGq4qFV5_3BOlKpA!G$90sBGiXn`Fk1 zwr|5dTRWh-kRVh_n1}oCRkKEWJSNFgkqPs5TsLTDP zdy^NF{i)*pYmSbbZ;FcL-P&E$k8QmibKluZG5?K*+9Qesnxm;v7|7>r%J>)%U#5Qy ze#Um6RQs5qT}=bj+pkg7HS|hgKhU5%bvqBUWD4j`h%%3wNU}g4&CRn9IDJ#zY#1w_ z-Ue*0>FqhlOPPaRJlwA%3Ce~5?O@2~_JDctqQ?3AFG zMkK`?o{mgSM@Ikpudw{%iD>n?>4``XTYC*#Njm>pRsJoA3V`s{T{8TLiuw99Xgg7j zm0Mt%k>G^Uhc7TNIESM{NL(9VgX<7&58qT+RHNb_;>y2|1Yy3aKJh!C45&qwgq@Sx za(&uxu+bMixQ#gE?e!^)FC~DWVzt}Km69`RjC^i+4#ID>2OugAa`Gj`7VclL%`@|t zH%qG)Cl@9Yy-6l{Iq}`qriYi;2d~J3S71MDt(2c03zY<(`~ zQJ1sN&OGL5s6V!IKyyS*hv?>vsBI%+)VQt};caA!E_R?h$91PyyNkhiy@fGAkVnw+ zx%8QA8C_Je<7$pE5hdL@C!%CKmv{iQ8EohYtCjevLH!CoP2Rd z$fmGUvlEaDb577%+l|{mkaHCQrqvU=Y+8Rov1%99ysZX!4t1ZHGYY^Y84wM*%GvmV z2v@62U8)TNX(@(I1+7zHjoN}ACQ#1 zt^vB6ieyig{-1CXX>dG=mdhTn55G=ojAv5)ycWQ(csqak~IfvHD~=vEq(xr0>Np ze+^5Kz5s%fR{_qy-OtvM-w{}@BDEcc+Qow zd+!P@7k2=3hud{Oytve`@LHlVN&j(2lD)e}F4?!TLw0w@2cUnIH&MTQBz_i$@+?)! z?#B4Q2GsV-B~2tZ3?fa37Y;8Sgbf`j?cNg~!0lm@L6$g}bf-)9sTpJ>)RbhCLSimm zeJDQkG!OJ^OTLqY7<1>E+CzmJno{gevAW}BksDVe*52K zl(rEu-S}v#05ZjBM2Lod`NT(89pEUyhax^W3&u!|Pj8oBmKFOvV;g49gGM3Fn^9U@ zk8|j)n=$?c;tV_8F^d`pi^3d41%U_?W0))CHiM9}za_5ZhJybHnO0&uyaIwQqjf~` zyupxHGb*`85)v#es#ER6WaeOG8sse!hQua=f6Yz`{*b}TF@Qj^2d09ADK#}3(xD3U z{Nx)-ky@98QQSj9q&N-RzM*UiwHo_%Y1UB#kS~XcWqMPeI=1#y(ZkzF^~*Z`1FT>A zGaxA2kSO(qr)J&LCVSc*h#$|~n^_U=&!s)bI{fw#V*K}dgJaJ$$yt_>{>0B z8;{E6$KtP|-*rT;Zi^2=q}SWLUfU(tc6}=T^yViw|6Kg}%+F>XhSDdlqz_+B*S-Pp z6Xm-)1$NurPUPO(m^39LE7$MM%DcPeTJXKAp1SK1M}A>U$UL;-UjYxid(CkqkI0tx zl~a$)Z78g}oPCFx#~n1*4|6~Ri0-w5Y)o9Xe}anUfg?+fXINQlh(|-36KlM#)-|Sw z%_JR5=G_8`dtiYs^q=!}9)w>zSfpvMTqX%hR=TPEpwR2D(%3(l&Z7KuCfDMrW|9^HO zHP(}gvaAR018sC4-lemdIr*56Fl8QR1%94@J8Do!Oak(hAzG_5pn3->Z2b=)7&6R~ zH}0yf6)YK*Z+{W4>U)CO(cvnFMz<8Wif?*LfvXi;F2N~WMYOi1z*R(HTMAtDZn*@f zaCKMSs||UtHobUt)Q;Iz5aB^fXf{z0x6UR~FyD7Te8u#;=eOa2mKVp3v})JJ_iDB5 zk!K;RTtCgXXE2Uda!biNzl4i2N;4;xwSP+&INfHR126W-?k@1=2NsRjXnfm;^0Ws0 zV`!s>JTdZ33b+PkbCyWAb`Gw=mRLMdhY)`!>M%DBZySo1svZFZdIZ1*5%2=0Lp&aO5OTr}_ssU_OPN2>W?edtSwJ3!o8*!G^_;aW!Pq;Pk#XQUu=P^Kk2^#zi1I9#R3#Bi9f^M z$l$8auWlA71wnM3h}IT=iZ>gP;7h`sHDL;#5DzcgBfww5pgmFFV07;`iae*a6E*oX z5Y*(2wld5j0Y3nJ`$X%Sd*5bhbE@?_4=WxzQoGNkONUafp)bm6Ne0@mmaMW?HuydS z!sa^-d25@{$hquHa_O?Rc@9v?j-BhSeX?sGYyz&k8e~@kR2GXCipcqH*|i(b3l|E> zIYhnMNu^bGwUSDc>}n#FM%mR!D%j^K4z@>j?Rj2axU(oe_`HTHulbUq3mfLyU%}qv zl@II-92k+p23UcsGUeR037h4k%cFNDmM1n#s@6-I<&x%&lBSJP?}x=7dG31F>pJAR zj+NGQ-SL#SCslF+_R4FE=j~q?P=(GPy!QTUKQi61EL+I4+0$hQS0lf43~V@D>yB#K zQN1K2-Xa0-nv|nPi-D98Q`eMuCjm}T>zZQ^#7@W$Udl3-?WE^q1@(&x+sUKMFY1d< zb}_%`;-KEb1iu8#9UOQ7!CXXEB7&S2H~s(*naSr<3Vgu9{51Sj(5EZ(?}%tD zMATyjb|x%Nc$%;?;bg+`gs};qC$PsKAt6FR*!8dA^d2Q*awmyCiRj7@g4R}g8_0VU zyx;OFa6>n|bzj-6+=(wstGMzndptlotw2hPfb6mXDJ#Wmb|6&+Kq|~YiXA{|EI>*s zfgHhZ2W{N$uS=V_GIeI0w53@53pglTXnJyLR`nx@Xxk)|byPIspJIao2+LvU4uWIi z4@nOx5QHA6b`?8MegN49sih;T_}a;jsnp+SiD;DIcNN&a4bsdY-QtnSfnzVf`rswa&ro5By#EO+G7(bmyYF~-}`=V z=Dqnn5by!Erlgy>e?d3m1R+z3w_Zq&m4+0D|pQc$Yf0|lYGon6gzL=J7y7Q9Z!6D z7(ncQvdU`Q0bXEp2`KQYTO0ri(mA3D%itm0ZTj4Tw&T%VytXZ&EEK$)+uqBt;GTG! z-R^BLl@_FjmzF-LZkqt`_&#JIG*ml4mx z?&jRG$ay>q_qJJAoTFJ*nEQaOUn|k^8Z9$WT&tt-IX?* z`#M|)jmSxl(~v@5V_#psSX#3)CDmS~0$HZstZ9^VY-5eeD=KSz?M}Ge?TH2}86`4H zj(W`H0?yhNMLBE}r}Jo_)EJG-ZD&qsz$E!xzNqS%73PbIZXD4p;UWz-HQAswWR`jx zQ^B;TvaDLTs2Z5ChX7F7vQ>M{B8o$xqPb4}tt2z5O#_$^lVl7wkZKN7?}r;%T(a|K zks`|`Ntwz8b2*dOC}KPHHp7~PMqbUJaR_jQLTowZCERuP>HK?3Ofg4PgUxh;LAU0m z`GQ)^nM;!smQ})M6vm z*hnolR*j9-5r|(s0Ycojr@Zyt2mJBFaM#|2Rl;fXpB1D%iN4nmPysJ|NOuZ4!I zq2a^mg`I_O7i!VLYIJaK=$-xOyNA&?zYl-6a4Mm2=L^J)>ORmjR7q{$IE)YfSpK2> z;BF=LhyUVn_a%pUkeI5Zp89)_m5%$$J>?tUie808xq@clB1#w(sy_ z{3rO}S_M5-x(^x#_kH(#FY8{Wb7sBGPUEY&zSJ!EH8F=$SI}<*a~?)Lz9OZjJ&z|` zJnNmGL{Gr*{3v=dDlwe&Q#p%uOg4%0veK~TUetKjBzWBEG^^QGhN)rQYIL%%)fJZ8 z$HaMnsK05tX6Korb2;uU{Q^!H1Z0SP&Xo6=pEVtSK*kyAyu((Ked-88cm(>6K>rcA dd<3rk3A{(((lPWu7ZNaXf`AZk5gB9J{{i`Nn5X~% literal 0 HcmV?d00001 diff --git a/mastering/__pycache__/urls.cpython-312.pyc b/mastering/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4aecef66997e19d2833e2f41562ab20250916aa GIT binary patch literal 594 zcmZXQyGjE=6ozNAyUFIJqJ@Y;QbuVUHcW}lh@goMeLfjyV zC^nIW>lh;%qmmi3q`HJr3^7b)CbiV@$H`5V$M}OZ@*^%GxTxZU0Y%k6TvBkE-%P}% zCY&zS6Bp@H<^vu|IFw!DH$TJ>;3#JXJNH5qW&+oA2yL7>C&UEGCKkI~XgSA~%^)!7 zhENTIRx6%Q{J^V(DF}?5%f*5l+-XyTg29#a==ZG z!~8rBMgaqB(K)dUTh9yGVkf?*ZAV$P)s=F- z7S)8;tz9h|?xzhA(nbw|f(FX&#ZcX}92%Ic%4b9YSdv#0=2OBLzoDf!H1`!pO7eR4 z`lz4Hb+fs)-ILc|Wwn!BZnJ)2y<1p+(t7e<6e`>){o+oyxYOBZJ^A!AAy3Dy(tJu} F{02fin;ZZD literal 0 HcmV?d00001 diff --git a/mastering/__pycache__/views.cpython-312.pyc b/mastering/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1383da5d5679f56c446a12cf3e468c2ad1c89a4e GIT binary patch literal 5272 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 literal 0 HcmV?d00001 diff --git a/mastering/admin.py b/mastering/admin.py new file mode 100644 index 0000000..ea5d68b --- /dev/null +++ b/mastering/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/mastering/apps.py b/mastering/apps.py new file mode 100644 index 0000000..b167ab5 --- /dev/null +++ b/mastering/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MasteringConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "mastering" diff --git a/mastering/forms.py b/mastering/forms.py new file mode 100644 index 0000000..b3b51ca --- /dev/null +++ b/mastering/forms.py @@ -0,0 +1,10 @@ +from django import forms +from .models import VocalMastering + +class VocalUploadForm(forms.ModelForm): + class Meta: + model = VocalMastering + fields = ['original_audio'] + widgets = { + 'original_audio': forms.FileInput(attrs={'accept': 'audio/*'}) + } \ No newline at end of file diff --git a/mastering/migrations/0001_initial.py b/mastering/migrations/0001_initial.py new file mode 100644 index 0000000..c565826 --- /dev/null +++ b/mastering/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# Generated by Django 5.1.4 on 2024-12-10 09:10 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="VocalProcessingJob", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("original_file", models.FileField(upload_to="original_vocals/")), + ( + "processed_file", + models.FileField( + blank=True, null=True, upload_to="processed_vocals/" + ), + ), + ( + "status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("processing", "Processing"), + ("completed", "Completed"), + ("failed", "Failed"), + ], + default="pending", + max_length=20, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("completed_at", models.DateTimeField(blank=True, null=True)), + ("error_message", models.TextField(blank=True, null=True)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/mastering/migrations/0002_vocalmastering_delete_vocalprocessingjob.py b/mastering/migrations/0002_vocalmastering_delete_vocalprocessingjob.py new file mode 100644 index 0000000..0b691a6 --- /dev/null +++ b/mastering/migrations/0002_vocalmastering_delete_vocalprocessingjob.py @@ -0,0 +1,37 @@ +# Generated by Django 5.1.4 on 2024-12-10 09:46 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("mastering", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="VocalMastering", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("original_audio", models.FileField(upload_to="vocals/")), + ( + "mastered_audio", + models.FileField(blank=True, null=True, upload_to="mastered/"), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.DeleteModel( + name="VocalProcessingJob", + ), + ] diff --git a/mastering/migrations/0003_vocalmastering_reference_audio.py b/mastering/migrations/0003_vocalmastering_reference_audio.py new file mode 100644 index 0000000..3c2b6ca --- /dev/null +++ b/mastering/migrations/0003_vocalmastering_reference_audio.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2024-12-12 11:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("mastering", "0002_vocalmastering_delete_vocalprocessingjob"), + ] + + operations = [ + migrations.AddField( + model_name="vocalmastering", + name="reference_audio", + field=models.FileField(blank=True, null=True, upload_to="references/"), + ), + ] diff --git a/mastering/migrations/0004_remove_vocalmastering_reference_audio.py b/mastering/migrations/0004_remove_vocalmastering_reference_audio.py new file mode 100644 index 0000000..c07d543 --- /dev/null +++ b/mastering/migrations/0004_remove_vocalmastering_reference_audio.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.4 on 2024-12-12 11:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("mastering", "0003_vocalmastering_reference_audio"), + ] + + operations = [ + migrations.RemoveField( + model_name="vocalmastering", + name="reference_audio", + ), + ] diff --git a/mastering/migrations/__init__.py b/mastering/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mastering/migrations/__pycache__/0001_initial.cpython-312.pyc b/mastering/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa0313b519260dddc5ced52fbc38acefb8641293 GIT binary patch literal 2073 zcmZ`)&u`OK9DjCV$8pn=1h=E@);1=k$)bwa`JCrl^wT=Dh|7$IfgA zC?_Uy*pb_IWV?*1`2#p|=aOiZ5wYzZI_t{As)Z&Bv{qcF<_j%v<{eJy& zcvwd8JQlt$k4p&s!<7Cb^n|^eApDFl!kmeU9DI3`uZD^tj@d$HxGEHds#p{`#G@++ zhaV#>5TP>C4|Mq52?cU7vdv}1*G##d<*4Km&oivDo8_roHOh|e8Mft8v1(&tx~0Pa z2%YFDdkZ{cMSMm@9&^PIk4U)UpJ#9ZLPR{43m2fuTv75@Cf{DgG|XcbRj!O3I|5FACyqrOYQ*}P0_?Dloj4k^jPS^B96o{Ljl=*x z>Equ&ipN~zdv-ymu%ChOB_QtjfaHB?h$9xc8O-`RYHet$0!??ZfaYG zNgl=z)O&0z_Yfmh5WHB1mv>l)IVaf( zhKb|Yj#h=d>t#a2b;u{9XImO{Jn*BuXM-W^Cso`sh>6|n*)b|7h8 zW199uT{jsRw1{CD9^8(@`jRT{eZ5xGHvxzVOBS(8+YTE<^u+S*^{=(tw=Un*R&V4l zuh7^5!hrJ*<@~E(Wkbke(~!o#GRn(P6a~hIMhlR$o|wC%JApWyhjLjrs(sHodEf;e zxnMiQC|ln^0g1SxTV;9g-k zR^eV}K&+6G=WgWvKpSTl=4V!Q*MqtQ@AB0RzksxA-?T9x^*g}bSXfv%tM$?~U)y#f zu#^9Sp8;~$U|>B)uSSxOu5?8d&HN;{rMZ?g_e^SMF10e3{*;!xAzl$5t#oA+oo4Q- zmNeB#%mRFLyraxLDYTWjmNM6w&34k6j>?4ns4zO#6{JM6n?%V}Q$6?eOj|wIQqMIn z+}Ksun(=R6Dd~=qW{cA;5U12btX@@Cl_0ji~B-| zF%W@4j>{+``@%<2xf}X8DgJoPx!6x}R-)rU70lz!`QTSK-!BE{Q-IN-p1)*UTS3W2 z4_5fq?U!Y6YZq8tKlqm*y^+2My5pjg1C{-GToqp35XW(QBH~hip_#u?W?x+5#3yn) Oo^Hj{{~=KP827%Q6rTOf?)nGEb*pl*)5cA+s+6X6#Q`BCgyoWoR0y>QS(a97&%_#McGumX zBKHJR4jg-fBb)%m2abqS?_6RjlC8Ob#EF|l!lftP?AnPd#2ntfH}89IzW1}g6$%!D z^LOF%{v8#eKZUV8+Cn*Nf$|N)2ulpL#aCvMC%0u!X)6+v(L;olBZO5V4~%8-<@Z?0 z^tL*ZZ0#>$Jho+Sd9)w8F%A5P>t28fi+Yzq2;~;WSyd9R6V#S4YRg#aOKn9)L>;8e zS3?jm!@{Qzif`Tvix`9Qe{ZphHLQ;HW2j!70~xQS87i5*DzPy(kAaOifV2J=r$Nr# zIKQkMu!R(REsI^1SQ{5FbN1EoTgP(dhX{3WeHEMOxHMj0M)c)&VN|xSaC3jzO=zv@ zxlv3)>i2nmcX#)mLwy=km+|6fLCdKj_@s-PyR9%*F?MowTve#V9)~+H%%W2gZBfNs$+B z2WkKw*)6=X=+hwR?hHqv4pdUH21@iRh$A%Dk-0TjFe zU4462slRMZEA>gGKHI4OV0~}RZfwo!+q0Xu&rR*>wYg@jm*-_vzIn3Q`0kzQW@ED1 z_=TQsesogW`vZb5<9~ecGk$e=TKROYDG+0za`mLv`1bj<)|k{9uPUdt)=BB%yjH3d z&$TP2HQ#=N&#iMshRZwG%lcQ3!mVX7wiebv=O#{@xr4juT+>lzCEpUz6n`7E*T#F< qa=ahfV2Xb_5((ytBuQsFlJdW!?Q^{<=`XEmsXi&y|3P4+3H}AT?OaFz literal 0 HcmV?d00001 diff --git a/mastering/migrations/__pycache__/0003_vocalmastering_reference_audio.cpython-312.pyc b/mastering/migrations/__pycache__/0003_vocalmastering_reference_audio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..564a04fa3471b4b921aa6720047894700603eb79 GIT binary patch literal 855 zcmZ8g&ui2`6n>Lr(2g_IEk}(aR(urQk zIaw$W?r01Q+Z1NA(1Zfb$jf>>_2Z7XU$566(YHz43+F+Kig}Muqun%V^DM*MAZdx^ ztC`|@%J+E6<2I*WpZUq_gKP6h_P|y}sTF$hzR==+7z(rB4HJ*iUVFy&PL$u79vjjkTe%HoAFl zbfY>dSC8(VmaD^Z_2cT7#@FVL@~g82&9ug8sCwQ_56L#D^x wl4*JF9Yp8G)2Y9k={i#UKOmD}tP(>0>Okx>eUa!#*6GT|aAo5UAjzx$0VXi$IRF3v literal 0 HcmV?d00001 diff --git a/mastering/migrations/__pycache__/0004_remove_vocalmastering_reference_audio.cpython-312.pyc b/mastering/migrations/__pycache__/0004_remove_vocalmastering_reference_audio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e02bc3cad15848259589e9e0b30bad02cc68e70c GIT binary patch literal 714 zcmah{J#W-N5S{fG=M8}nB4|)JAX4Ol(;=v$1PLhufk+BU%hJl+4wg^;IPANK>xh&h zWxD(Zl>8HDQYG37iHgqBB~2=3eQ^?K*kb%<{AOm~(|(_w4?p_C0DkGDIrasZ ztPt1-36dp{GM_Op;0;J)2c(ItZ#4~9-ve`sePe8S_HUhdOz||1if|a`S=qbd0yG0A zt5n?w1fNOp4ap+rn+BlOuP=GM1!%oA53K`ZS|z44wmnC=8_f>SP;MHQLoDJfQct?w z?sKt~_rm0|C<^?91!g@I;Yh~0y7l*eb^9LzjpS)AF%emqqOx?{^lqq@s0%_UE(8%q z2@<|3#OG0%Xp+t(4lt9nEyl9Y{-~J`nC4sfI>tmQo)7TCoNqAEs?N99SAs#2lV>SO zXcO#XZ&(KF{VJ13lgpu|HqEbU|i@oPzQ%V z`IN#AR1Uanm1!(EU*A_8+=sjTxV3oHT0FVGd~)aUscpKvGVOV1`(1Ij>EHBB<;wcv z9r>}=KOH`=88vNz(0OZh>fg)a9;Ngop?pB0G8toM4zNe3&J1(*`0?D*(cID*P*QjQ E0V}S+y#N3J literal 0 HcmV?d00001 diff --git a/mastering/migrations/__pycache__/__init__.cpython-312.pyc b/mastering/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4ba4952a3eb3a62e9c19679efde712c8337cfe9 GIT binary patch literal 192 zcmX@j%ge<81Uopw(?RrO5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!ig&S!DJaTMFG|eK z%u81QGP6>XONwJ$vJ&&s^J5GR^b~v(i%U|AK(daWF=hG5i8=APFrgSERwmGplFa

threshold + gain[above_threshold] = 10 ** ((threshold - audio_db[above_threshold]) * (1 - 1 / ratio) / 20) + + # Smooth the gain using a moving average + window_size = int(self.sample_rate * (attack + release)) + if window_size > 1: + smoothed_gain = np.convolve(gain, np.ones(window_size) / window_size, mode='same') + else: + smoothed_gain = gain + + return audio * smoothed_gain + except Exception as e: + print(f"Compression error: {e}") + return audio + + def equalize(self, audio): + try: + fft_data = fft(audio) + freqs = np.fft.fftfreq(len(audio), 1 / self.sample_rate) + perceptual_curve = np.interp(np.abs(freqs), [100, 1000, 5000, 20000], [1.0, 1.2, 1.0, 0.8]) + return ifft(fft_data * perceptual_curve).real + except Exception as e: + print(f"Equalization error: {e}") + return audio + + # 3. Phase Alignment + def phase_alignment(self, audio): + try: + if audio.ndim > 1: + left, right = audio[0], audio[1] + delay = np.correlate(left, right, mode='full') + alignment_offset = np.argmax(delay) - len(right) + 1 + if alignment_offset > 0: + right = np.pad(right, (alignment_offset, 0), mode='constant')[:len(left)] + else: + left = np.pad(left, (-alignment_offset, 0), mode='constant')[:len(right)] + return np.stack([left, right]) + return audio + except Exception as e: + print(f"Phase alignment error: {e}") + return audio + + # 4. Psychoacoustic Modeling + def psychoacoustic_enhancement(self, audio): + try: + spectrum = np.fft.rfft(audio) + freqs = np.fft.rfftfreq(len(audio), 1 / self.sample_rate) + masking_curve = np.ones_like(freqs) + masking_curve[(freqs >= 2000) & (freqs <= 5000)] *= 1.2 + return np.fft.irfft(spectrum * masking_curve) + except Exception as e: + print(f"Psychoacoustic enhancement error: {e}") + return audio + + # 5. Transient Shaping + def transient_shaping(self, audio): + try: + # Calculate RMS envelope with a frame length of 2048 samples + frame_length = 2048 + hop_length = frame_length // 2 # Overlap for smoother envelope + rms = librosa.feature.rms(y=audio, frame_length=frame_length, hop_length=hop_length)[0] + + # Normalize the RMS envelope to the range [0.8, 1.2] for gain control + gain = np.interp(rms, (rms.min(), rms.max()), (0.8, 1.2)) + + # Upsample gain to match the length of the audio + gain_upsampled = np.interp( + np.arange(len(audio)), + np.linspace(0, len(audio), len(gain)), + gain + ) + + # Apply gain to audio for transient shaping + shaped_audio = audio * gain_upsampled + return shaped_audio + except Exception as e: + print(f"Transient shaping error: {e}") + return audio + + # 6. Mid/Side (M/S) Processing Refinement + def mid_side_processing(self, audio): + try: + if audio.ndim > 1: + left, right = audio[0], audio[1] + mid = (left + right) / 2 + side = (left - right) / 2 + side *= 1.2 # Widen high frequencies + return np.stack([mid + side, mid - side]) + return audio + except Exception as e: + print(f"M/S processing error: {e}") + return audio + + # 7. Advanced De-Essing + def de_essing(self, audio): + try: + D = librosa.stft(audio) + mag, phase = np.abs(D), np.angle(D) + high_freq_mask = mag > np.percentile(mag, 95) + attenuation = np.clip(1 - (mag / mag.max()), 0.5, 1) + mag[high_freq_mask] *= attenuation[high_freq_mask] + return librosa.istft(mag * np.exp(1j * phase)) + except Exception as e: + print(f"De-essing error: {e}") + return audio + + # 8. Intelligent Harmonic Exciter + def harmonic_exciter(self, audio): + try: + harmonic_audio = audio + 0.01 * np.tanh(audio * 2) + return harmonic_audio + except Exception as e: + print(f"Harmonic exciter error: {e}") + return audio + + # 9. Multiband Compression + def multiband_compression(self, audio): + try: + bands = [(20, 200), (200, 1000), (1000, 5000), (5000, 20000)] + compressed_audio = np.zeros_like(audio) + for low, high in bands: + sos = signal.butter(4, [low, high], btype='band', fs=self.sample_rate, output='sos') + band = signal.sosfilt(sos, audio) + gain = 0.9 # Example fixed gain, could be adaptive + compressed_audio += band * gain + return compressed_audio + except Exception as e: + print(f"Multiband compression error: {e}") + return audio + + # 10. Loudness Range Optimization + def loudness_normalization(self, audio): + try: + meter = pyln.Meter(self.sample_rate) + loudness = meter.integrated_loudness(audio) + return pyln.normalize.loudness(audio, loudness, self.target_loudness) + except Exception as e: + print(f"Loudness normalization error: {e}") + return audio + + # 11. Stereo Enhancement + def stereo_enhancement(self, audio): + try: + # Ensure audio is stereo + if audio.ndim == 1: + raise ValueError("Input audio must be stereo for stereo enhancement.") + + # Separate mid (center) and side (stereo) channels + mid = (audio[0, :] + audio[1, :]) / 2 # Mono channel (mid) + side = (audio[0, :] - audio[1, :]) / 2 # Stereo difference (side) + + # Amplify the side channel for stereo widening + side_widened = side * 1.2 # Adjust factor for widening effect + + # Recombine mid and side channels + left = mid + side_widened + right = mid - side_widened + + # Normalize to avoid clipping + left = np.clip(left, -1.0, 1.0) + right = np.clip(right, -1.0, 1.0) + + # Combine left and right channels into stereo + enhanced_audio = np.vstack([left, right]) + + return enhanced_audio + except Exception as e: + print(f"Stereo enhancement error: {e}") + return audio + + def normalize_vocal_loudness(self, audio): + try: + # Calculate the loudness of the audio + audio_db = librosa.amplitude_to_db(np.abs(audio)) + target_db=-20 + # Identify vocal segments (this is a simplified approach) + # You might want to use a more sophisticated method to isolate vocals + vocal_mask = audio_db > (np.mean(audio_db) + 10) # Adjust threshold as needed + vocal_segments = audio[vocal_mask] + + # Calculate the current loudness of the vocal segments + current_loudness = librosa.amplitude_to_db(np.mean(np.abs(vocal_segments))) + + # Calculate the gain needed to reach the target loudness + gain = target_db - current_loudness + + # Apply gain to the vocal segments + normalized_audio = audio * (10 ** (gain / 20)) + + return normalized_audio + + except Exception as e: + print(f"Error in loudness normalization: {e}") + return audio + + # 12. Export and Delivery Optimization + def export_audio(self, audio, output_path): + try: + # sf.write(output_path, audio, self.sample_rate, subtype="PCM_16") + + # Convert the audio to 24-bit integer format + audio_24bit = (audio * (2**23 - 1)).astype(np.int32) + sf.write(output_path, audio, self.sample_rate, subtype="PCM_24") + print(f"Audio exported successfully") + except Exception as e: + print(f"Export error: {e}") + + def process_vocal(self, input_path, output_path=None): + try: + if not os.path.exists(input_path): + raise FileNotFoundError(f"Input file not found: {input_path}") + + audio, sr = librosa.load(input_path, sr=self.sample_rate) + if audio.ndim > 1: + audio = librosa.to_mono(audio) + + steps = [ + self.normalize_vocal_loudness, + self.phase_alignment, + self.de_essing, + self.dynamic_compression, + self.equalize, + self.psychoacoustic_enhancement, + self.multiband_compression, + self.transient_shaping, + self.harmonic_exciter, + self.mid_side_processing, + # self.stereo_enhancement, + self.loudness_normalization, + ] + + for step in steps: + audio = step(audio) + + if output_path is None: + output_path = os.path.join( + settings.MEDIA_ROOT, + 'processed_vocals', + f'processed_{os.path.basename(input_path)}' + ) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + self.export_audio(audio, output_path) + return output_path + except Exception as e: + print(f"Processing error: {e}") + return None \ No newline at end of file diff --git a/mastering/tasks.py b/mastering/tasks.py new file mode 100644 index 0000000..ff00cb4 --- /dev/null +++ b/mastering/tasks.py @@ -0,0 +1,29 @@ +from celery import shared_task +from django.core.files import File +from .models import VocalMastering +from .processors import SmarterVocalMasteringProcessor +from django.utils import timezone +import os + +@shared_task +def process_vocal_track(job_id): + try: + job = VocalMastering.objects.get(id=job_id) + processor = SmarterVocalMasteringProcessor() + processed_file_path = processor.process_vocal(job.original_audio.path) + + if processed_file_path: + with open(processed_file_path, 'rb') as f: + job.mastered_audio.save(os.path.basename(processed_file_path), File(f)) + job.status = 'completed' + job.completed_at = timezone.now() + else: + job.status = 'failed' + job .error_message = "Processing failed" + + job.save() + + except Exception as e: + job.status = 'failed' + job.error_message = str(e) + job.save() \ No newline at end of file diff --git a/mastering/tests.py b/mastering/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/mastering/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/mastering/urls.py b/mastering/urls.py new file mode 100644 index 0000000..66b1c52 --- /dev/null +++ b/mastering/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.upload_vocal, name='upload_vocal'), + path('job//status/', views.job_status, name='job_status'), + path('job//download/', views.download_mastered_audio, name='download_mastered_audio'), +] \ No newline at end of file diff --git a/mastering/views.py b/mastering/views.py new file mode 100644 index 0000000..4a583e6 --- /dev/null +++ b/mastering/views.py @@ -0,0 +1,85 @@ +import logging +from django.shortcuts import render, redirect +from django.http import JsonResponse, FileResponse, Http404 +from django.views.decorators.http import require_http_methods +from django.contrib import messages +from django.core.exceptions import ValidationError +from .models import VocalMastering +from .forms import VocalUploadForm +from .processors import SmarterVocalMasteringProcessor + +logger = logging.getLogger(__name__) + +@require_http_methods(["GET", "POST"]) +def upload_vocal(request): + if request.method == 'POST': + form = VocalUploadForm(request.POST, request.FILES) + + if form.is_valid(): + audio_file = request.FILES.get('original_audio') + _validate_audio_file(audio_file) + job = VocalMastering.objects.create(original_audio=audio_file) + _process_vocal_async(job) + return redirect('job_status', job_id=job.id) + + messages.error(request, "Invalid form submission") + + else: + form = VocalUploadForm() + + return render(request, 'mastering/upload.html', {'form': form}) + +def _validate_audio_file(audio_file): + if not audio_file: + raise ValidationError("No audio file uploaded") + + max_size = 50 * 1024 * 1024 + if audio_file.size > max_size: + raise ValidationError(f"File too large. Maximum size is {max_size/1024/1024}MB") + + allowed_types = ['audio/wav', 'audio/mpeg', 'audio/mp3', 'audio/x-wav', 'audio/x-m4a'] + if audio_file.content_type not in allowed_types: + raise ValidationError("Invalid file type. Supported: WAV, MP3, M4A") + +def _process_vocal_async(job): + from threading import Thread + + def process_task(): + try: + processor = SmarterVocalMasteringProcessor() + processed_path = processor.process_vocal(job.original_audio.path) + if processed_path: + with open(processed_path, 'rb') as f: + job.mastered_audio.save(f'mastered_{job.id}.wav', f) + job.save() + except Exception as e: + logger.error(f"Processing error for job {job.id}: {e}") + + Thread(target=process_task, daemon=True).start() + +def job_status(request, job_id): + try: + job = VocalMastering.objects.get(id=job_id) + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return JsonResponse({ + 'status': 'COMPLETED' if job.mastered_audio else 'PROCESSING', + 'mastered_audio_url': job.mastered_audio.url if job.mastered_audio else None + }) + return render(request, 'mastering/job_status.html', {'job': job}) + + except VocalMastering.DoesNotExist: + messages.error(request, "Job not found") + return redirect('upload_vocal') + +def download_mastered_audio(request, job_id): + try: + job = VocalMastering.objects.get(id=job_id) + if not job.mastered_audio: + raise Http404("Mastered audio file not found") + + response = FileResponse(job.mastered_audio.open('rb'), as_attachment=True, filename=f'mastered_{job.id}.wav') + response['Content-Type'] = 'audio/wav' + return response + + except VocalMastering.DoesNotExist: + raise Http404("Job not found") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6d62c84 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +Django==5.1.4 +celery==5.4.0 +redis==5.2.1 +numba==0.60.0 +numpy==2.0.2 +librosa==0.10.2.post1 +soundfile==0.12.1 +pyloudnorm==0.1.1 +scipy==1.14.1 +noisereduce==3.0.3 \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4881be7 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,20 @@ + + + + + Vocal Mastering + + + +

+ {% block content %}{% endblock %} +
+ + \ No newline at end of file diff --git a/templates/mastering/job_status.html b/templates/mastering/job_status.html new file mode 100644 index 0000000..4b928be --- /dev/null +++ b/templates/mastering/job_status.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Job Status

+ + {% if job.mastered_audio %} +
+

Processing Complete

+ + Download Mastered Audio + +
+ {% else %} +
+

Processing in progress...

+ +
+ + + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/mastering/result.html b/templates/mastering/result.html new file mode 100644 index 0000000..58a4a0f --- /dev/null +++ b/templates/mastering/result.html @@ -0,0 +1,67 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {% if processing_success %} +

Vocal Mastering Completed

+ +
+

Original Audio

+ +
+ +
+

Mastered Audio

+ +
+ + + {% else %} +

Processing Failed

+

We were unable to process your audio file. Please try again.

+ {% endif %} +
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/mastering/upload.html b/templates/mastering/upload.html new file mode 100644 index 0000000..591fd4b --- /dev/null +++ b/templates/mastering/upload.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block content %} +

Upload Vocal for Mastering

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..119e05a --- /dev/null +++ b/vercel.json @@ -0,0 +1,15 @@ +{ + "builds": [ + { + "src": "vocal_mastering/wsgi.py", + "use": "@vercel/python", + "config": {"runtime": "python3.12" } + } + ], + "routes": [ + { + "src": "/(.*)", + "dest": "/vocal_mastering/wsgi.py" + } + ] +} diff --git a/vocal_mastering/__init__.py b/vocal_mastering/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vocal_mastering/__pycache__/__init__.cpython-312.pyc b/vocal_mastering/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f04c02b48691815ed291f5d5bbe133e87fd15895 GIT binary patch literal 187 zcmX@j%ge<81WP!=(?RrO5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!igK}vDJaTMFG|eK z%u81QGP6>XONwJ$vJ&&s^J5GR^b~v(i%U|AK(daWF=hG5i8=APFd+8&~Blv|OX(u<8AVh|``pr_uH{xI4Vi6fR63x~l1X6{ zkRm83MO8wgR04(Gl2Q!zFzj&@krF71!-#Sc1fh~Bh&)~VEvby=tSqR@JIft;``cSPcOTrmee<^VYD*RKQ71cqVVdohZEBm0f()(YTo{*j zA>x%Wq342Iw!PQEmNPx3w6JAq?bd090SZM0kIUm8wqk3|EltOk8#5Hk#-`R%A;pEE zp`jt9<6XtXjDKX!-eUys>_C)Hbk5yz++ilnh`%t9jdcea)04%Cu) zwrVN{u6Dd&)`(yBWxJ{9@=4K|uBltj;tggTjm{V(emHOq7z*-Fw&GoH|*~w z4U@rnJa#m{**c14?Y2!Mzf^P$`H3(M3!AL%J@>AS1eKmxI8XIkd zb&FZiRCt5!RSoN?lFzx(4$M|v+cbkvQJ0csZyw3A8#S@j)?Z>b(t-KYvR!J06?m@& z+_=CO>bxjF;eU3+9KX_7b7{6zs;={#{J1KJf}1K<1QA3afW8sjc)7@NC4QZ)^KP%e4|K@`9UQ7uJfNeW_Sry%uw094oS3SU>8}{1pg{DFI{3QYYlddmkU*nhnU4OFH2QuGpYe+DlR>! zxDk&MB{#$It8Akr%79jtSBrd!`-Ax8{SkeZxbHXMb*nH^V7ANvxs98)wZZvQVPokQ zGgCgE;xlY5 zUmv|s%pN3W|4LkYpO`;L%)d`89VC`|R6j+e>SQn8j}S4s7d}d7di2LQl?n7xAG1^{ z0Gce3zTQjrDI&|0hm)6HKRKAZv7eqh%;t`gGarfYwNyVC&Bi~(lHo`{n4$x{Sbv7d z-rpzEfF4dAW|+ft?)Afi^oa<~z70gq_2P%gsb1`mo_ZZUpud4=X(BeUpO*T; z0H9GmQyXP|8fEGOW{SQV*e7!R>%?TPM;|38d$FU$M2|XxBSrd2A~g-bapK~=10rM2 HL5cnYt*;Z% literal 0 HcmV?d00001 diff --git a/vocal_mastering/__pycache__/urls.cpython-312.pyc b/vocal_mastering/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0cc254aed6857c10270bb0b11944cf8e180a03a8 GIT binary patch literal 753 zcmZWmO=}ZT6n!)EW-`e*4WyQq;36nhBPF&AU4)QQNztM;rC60A5XPD5IAK1*n~9LD z)Qxp#R`D-L{Q>?5H--q+2gQXe!9}FIuDoy3X#%~A`_4W0>s;PfRm~uY$1%*iyeDXahcz6>f-8EGCdC-LDNoj94)MqmOwr<6xn$_{ zZC#$?1Re4d6!%Hsv6(sqUCff4&1;G!wU#FAtS4731$t!2=4bf{2KLHD_OqPK?WqW9 zN;ln27c+lYVUeSvZhDSioP>5*gfq`3MvMZGys z4=?3=9!xX{g;N z5@Qyp-TU#5(nJJdzeaKBL>9$t3RS>^dXVFXRIF{@+q+NG+ncqmjpp9&Bbu4O-Fp4W zCv*bM==5o=+-Q@)CORB%7XY>_BMKX}Sa4%)wN%(n?kj9;HC_dV?l#X)z7R7RrFQpJ zu6Tjjajhy@gf;A2LmS*!;5hCW6@H;*@W<%ZKM9HHgQX952IYQvu-;!E;cGv!e2{zp z?AyZfNWDJ7H^!onU}60K5JJ$=$va literal 0 HcmV?d00001 diff --git a/vocal_mastering/__pycache__/wsgi.cpython-312.pyc b/vocal_mastering/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0ecba6cadae14ae41a613971e15cc610f4e52b0 GIT binary patch literal 691 zcmZWnL2DC16rS0QP1x8VVvEN?yaaSNId~}|6w-!Nn;udvgurIH^Rn46JG1Oe)26qc z{0aVqBK;{|Dhk40RK$ZfCAXfOO&S~OJIuT{kMDiodv89ht~$urdFdcL#|Zt%gM}5Y zo#~-*-XVs%h+$@N+{Na$xfR=88zaD@(gJdGkIFax!ea$?=favt7+DDHi|bT(WnxuV zPQN+_oo0&!QVgPy3}i~iGN9a#snU=}A|y#FM-XV&aax+dNg@>}qK81{K*~8iGKe@l zAqv15YMrPJ&to!BE*nu1%7vyt#vTaonOpaU@>olcLBi!E2BE!U6-J)(D{r@5#o2=p zw4cBEOER+c#?EHDx#xGb_V-)uX2;*%YrKB7b^7qu6kG*bn`x+OF)RAMk>dNAB~^BB zF31f`A8uYgwGClb1Tl_MDKf_Z84TzV*I9Xq`8}HE%C6otHhQL;Fs1R*uvM=y>$6v{ zxn%d>DmGiaTe|*>a|v!Txhg%M@8U&TF*N3%AyqTSZyRI$$138&^ghDQPgMVgthdgY f^98MaL~EDp?qzM~GkW>G^5|3L@mV>&Ye0ViZ7