{"id":22015272,"url":"https://github.com/oguzhan-yilmaz/pycrossfade","last_synced_at":"2025-04-05T13:06:18.271Z","repository":{"id":37681604,"uuid":"283192455","full_name":"oguzhan-yilmaz/pyCrossfade","owner":"oguzhan-yilmaz","description":"pyCrossfade is the result of a personal project to use beat matching, gradual bpm shift on bars, and EQ modification to provide smooth and tunable transitions between music files.","archived":false,"fork":false,"pushed_at":"2024-12-27T11:05:39.000Z","size":1137,"stargazers_count":124,"open_issues_count":1,"forks_count":15,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-29T12:06:25.865Z","etag":null,"topics":["audio","audio-processing","beat-matching","crossfade","dj","music","python","python3","transition"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oguzhan-yilmaz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-28T11:29:50.000Z","updated_at":"2024-12-29T03:58:11.000Z","dependencies_parsed_at":"2024-12-08T17:22:03.293Z","dependency_job_id":"a553f8a9-ce5c-431b-8f14-7430f0f7cb84","html_url":"https://github.com/oguzhan-yilmaz/pyCrossfade","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzhan-yilmaz%2FpyCrossfade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzhan-yilmaz%2FpyCrossfade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzhan-yilmaz%2FpyCrossfade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzhan-yilmaz%2FpyCrossfade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oguzhan-yilmaz","download_url":"https://codeload.github.com/oguzhan-yilmaz/pyCrossfade/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247339155,"owners_count":20923014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio","audio-processing","beat-matching","crossfade","dj","music","python","python3","transition"],"created_at":"2024-11-30T04:20:27.006Z","updated_at":"2025-04-05T13:06:18.243Z","avatar_url":"https://github.com/oguzhan-yilmaz.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pyCrossfade\n\n![pyCrossfade Logo](./assets/images/logo.png)\n\npyCrossfade is born out of a personal effort to create a customizable and beat-matched crossfade functionality.\n\nDecember 2024: ✨added CLI and Docker image✨\n\n---\n\n## CLI\n\nSince the creation of this project, Python3 and dependencies got updated and stopped working with the pyCrossfade codebase.\n\n### Docker Image\n\nI've created a [Docker Image on ghcr.io](https://github.com/oguzhan-yilmaz/pyCrossfade/pkgs/container/pycrossfade) to help users getting the correct dependecy versions.\n\n```bash\ndocker pull ghcr.io/oguzhan-yilmaz/pycrossfade:latest\n```\n\n### CLI Setup (for Linux)\n\nTo use the Docker image as a CLI, you'd need to:\n\n- attach your `audios/` directory to the container\n- attach a directory for persisting _pyCrossfade annotations_\n- set some Env Vars for easier use.\n\nThis can be long and ugly, so the best thing to do is create a `alias` command.\n\nChange the `MY_AUDIO_DIRECTORY` and add the following bash snippet to your `.bashrc` :\n\n```bash\nPYCROSSFADE_DIR=\"$HOME/.pycrossfade\"\nPYCROSSFADE_ANNOTATIONS_DIR=\"$PYCROSSFADE_DIR/annotations\"\n\nMY_AUDIO_DIRECTORY=\"!!CHANGEME!!\"\n\n# create the alias command\nalias pycrossfade=\"mkdir -p $PYCROSSFADE_DIR \\\n  \u0026\u0026 mkdir -p $PYCROSSFADE_ANNOTATIONS_DIR \\\n  \u0026\u0026 docker run --rm -it \\\n      -v \"$MY_AUDIO_DIRECTORY:/app/audios\" \\\n      -v $PYCROSSFADE_ANNOTATIONS_DIR:/app/pycrossfade_annotations \\\n      -e ANNOTATIONS_DIRECTORY=/app/pycrossfade_annotations \\\n      -e BASE_AUDIO_DIRECTORY=/app/audios/ \\\n      ghcr.io/oguzhan-yilmaz/pycrossfade:latest\"\n```\n\n### CLI Usage\n\n```bash\npycrossfade\n```\n\n![pycrossfade CLI help page](./assets/images/cli_help_page.png)\n\n```bash\npycrossfade crossfade --help\n```\n\n![pycrossfade CLI crossfade command help page](./assets/images/cli_crossfade_help_page.png)\n\n#### CLI Commands\n\n- `crossfade`: Crossfade between two songs\n- `crossfade-many`: Crossfade between min. of 3 songs\n- `song`: Process song and print metadata\n- `extract`: Extract BPM, ReplayGain, Key/Scale etc.\n- `mark-downbeats`: Play a beep sound on each downbeat\n- `cut-song`: Cut a song between two downbeats\n\n---\n\n## Older pyCrossfade\n\nBefore I added the CLI and Docker feature, I created the v0.1.0 tag. Access below:\n\n- \u003chttps://github.com/oguzhan-yilmaz/pyCrossfade/releases/tag/v0.1.0\u003e\n- Older [Scripted Usage](docs/scripted-usage-deprecated.md) documentation\n\n---\n\n## About This Project\n\nThis project's main goal is to create seamless crossfade transitions between music files. This requires some DJ'ing abilities such as _bpm changing_, _beat-matching_ and _equalizer manipulation_.\n\n#### Some Definitions on Music Domain\n\n- [Beat](\u003chttps://en.wikipedia.org/wiki/Beat_(music)\u003e)\n  In music and music theory, the beat is the basic unit of time, the pulse or regularly repeating event.\n  The beat is often defined as the rhythm listeners would tap their toes to when listening to a piece of music.\n\n- [Bar (Measure)](\u003chttps://en.wikipedia.org/wiki/Bar_(music)\u003e)\n  In musical notation, a bar (or measure) is a segment of time corresponding to a specific number of beats, usually 4.\n\n- [Downbeat](\u003chttps://en.wikipedia.org/wiki/Beat_(music)#Downbeat_and_upbeat\u003e)\n  The downbeat is the first beat of the bar, i.e. number 1.\n\n### About Madmom's Beat Tracking\n\nMadmom's Beat Tracking takes a long time to run, 45-150 seconds depending on the music file. It gives a `numpy array` as output, so when madmom finishes calculating, pyCrossfade saves/caches the said `numpy array` in a text file named after the song, under the folder `pycrossfade_annotations`. This makes pyCrossfade robust while working with same songs by avoiding heavy calculations every time.\n\n### BPM Matching\n\nThe creation of a transition requires two songs, called master and slave songs. Master song is the currently playing track and slave song refers to the next track.\n\nMaster and slave tracks can be in different BPM's or speeds, so before applying crossfade, we have to gradually increase/decrease to master track's speed to match slave's speed. Let's say master song has 90 bpm, and slave song has 135 bpm. This makes slave song 1.5x faster than master song. If we were to suddenly increase the speed 1.5x that would be harsh on the listeners ear.\n\n#### Gradually Time Stretching On Downbeats\n\nBefore applying crossfade, to match the bpm's of two songs, master song's speed is gradually increased on given number of downbeats. This ensures the listening experience quality. This works linearly as can be seen in the table below.\n\n##### Example\n\n```python\nfrom pycrossfade.transition import crop_audio_and_dbeats \\\n                                   time_stretch_gradually_in_downbeats\n\nfrom pycrossfade.song import Song\nfrom pycrossfade.utils import save_audio\n\nmy_song = Song('some/path/to/a/song.mp3')\n\nfinal_factor =  1.10 # times faster\n\n# returns a new Song obj. cropped from my_song's between given parameter downbeats(or bars).\nsample = crop_audio_and_dbeats(my_song, 50, 60) # sample of 10 bars\n\n# increases the sample song's speed gradually\nsample_but_faster_every_beat = time_stretch_gradually_in_downbeats(sample, final_factor)\n\nsave_audio(sample_but_faster_every_beat, 'some/output/path.wav', file_format='wav', bit_rate=320)\n```\n\n| bars                     | 1     | 2     | 3     | 4     | 5     | 6     | 7     | 8     | 9     | 10    | Final Factor |\n| ------------------------ | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ------------ |\n| _Time Stretching Factor_ | 1.01x | 1.02x | 1.03x | 1.04x | 1.05x | 1.06x | 1.07x | 1.08x | 1.09x | 1.10x | _1.10x_      |\n\n### Overview of the Transition\n\nA simple visualization of all the processes would be like this:\n\n\u003e _master song_ | _bpm matching_ | _crossfade_ | _slave song_\n\n\u003e ı||ı|ı||||ı||ı||||ı|||ı||ı||ı||ı||ıı||ı||ı|ıı||ı|ıı||ııııııııııııııııııı\n\n\u003e --------------------------------ııııııııııııııııııı||ı||ııı|||ı||ııı|||ı||ııı|ıı||||ıı\n\n### pyCrossfade's Approach To Perfect Beat Matching\n\nHuman ear can catch minimal errors easily thus making beat-matching is extremely important for any transition. Beat-matching would be easy if every beat had regular timing, but producers are doing their best to [humanize their songs](https://www.izotope.com/en/learn/how-to-humanize-and-dehumanize-drums.html), not playing every beat in regular timing to get nonrobotic rhythms.\n\n#### A Visual Explanation\n\nHere, I cut two songs between their 30th and 50th downbeats, resulting in the same amount of downbeats.\n![Escape.mp3 Downbeats](./assets/images/Escape-Downbeats.png)\n\nRed lines are denoting every _bar_, or it's delimiter _downbeats_.\n\n![Eyeillfals.mp3 Downbeats](./assets/images/Eyeillfals-Downbeats.png)\n\nThis is the second song with 20 bars.\n\n![Escape.mp3 and  Downbeats](./assets/images/Eyeillfals-Escape-Downbeats.png)\n\n\u003e First song's waveform is blue and it's bars denoted with red lines. Second song is shown with colors of orange and green.\n\nWhen we put them on top of each other, we can see that their beats(red and green lines) is not matched, resulting in clashing of drums - or distorted audio.\n\nEven though they have same amount of bars, resulting plot shows that second song is shorter. This is beacuse they have different BPMs - or speeds.\n\nIf every song had regular beat timing, then beat-matching would be easy as just time stretching the other song to match their speeds. However, because of _humanizing_, every bar can be different in length. For this reason, pyCrossfade applies beat matching on the level of bars.\n\n##### Beat Matching on the level of every bar\n\npyCrossfade lets you define every transition's length in bars, lets take it as _K_ bars. Then it gets _master song_'s last K bars, and _slave song_'s first K bars, and applies beat matching on each bar. This is ensures the created transition is perfectly beat-matched even if the songs are _humanized_ or not.\n\n### Possible Improvements\n\n- Better(maybe Non-Linear) EQ Filtering\n- Volume Balancing with Replay Gain\n- Developing a better Crossfade EQ Filtering\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foguzhan-yilmaz%2Fpycrossfade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foguzhan-yilmaz%2Fpycrossfade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foguzhan-yilmaz%2Fpycrossfade/lists"}