Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ jobs:
- ubuntu-latest
- windows-latest
py:
- "3.12"
- "3.11"
- "3.10"
- "3.9"
- "3.8"
include:
- os: macos-latest-xlarge
py: "3.10.11"
- os: macos-latest-xlarge
py: "3.11.8"
- os: macos-latest-xlarge
py: "3.12"
steps:
- name: Setup python for test ${{ matrix.py }}
uses: actions/setup-python@v4
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ To update Basic Pitch to the latest version, add `--upgrade` to the above comman

#### Compatible Environments:
- MacOS, Windows and Ubuntu operating systems
- Python versions 3.7, 3.8, 3.9, 3.10, 3.11
- Python versions 3.10, 3.11, 3.12
- **For Mac M1 hardware, we currently only support python version 3.10. Otherwise, we suggest using a virtual machine.**


Expand Down
4 changes: 2 additions & 2 deletions basic_pitch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import logging
import pathlib


try:
import coremltools

Expand Down Expand Up @@ -79,7 +78,8 @@ class FilenameSuffix(enum.Enum):


if TF_PRESENT:
_default_model_type = FilenameSuffix.tf
_tf_major_minor = tuple(int(part) for part in tensorflow.__version__.split(".")[:2])
_default_model_type = FilenameSuffix.tf if _tf_major_minor < (2, 16) else FilenameSuffix.tflite
elif CT_PRESENT:
_default_model_type = FilenameSuffix.coreml
elif TFLITE_PRESENT:
Expand Down
1 change: 0 additions & 1 deletion basic_pitch/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from enum import Enum


SEMITONES_PER_OCTAVE = 12 # for frequency bin calculations

FFT_HOP = 256
Expand Down
7 changes: 5 additions & 2 deletions basic_pitch/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@
try:
import tflite_runtime.interpreter as tflite
except ImportError:
if TF_PRESENT:
import tensorflow.lite as tflite
try:
from ai_edge_litert import interpreter as tflite
except ImportError:
if TF_PRESENT:
import tensorflow.lite as tflite

try:
import onnxruntime as ort
Expand Down
4 changes: 2 additions & 2 deletions basic_pitch/layers/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def padded_window(window_length: int, dtype: tf.dtypes.DType = tf.float32) -> tf
self.spec = tf.keras.layers.Lambda(
lambda x: tf.pad(
x,
[[0, 0] for _ in range(input_shape.rank - 1)] + [[self.fft_length // 2, self.fft_length // 2]],
[[0, 0] for _ in range(len(input_shape) - 1)] + [[self.fft_length // 2, self.fft_length // 2]],
mode=self.pad_mode,
)
)
Expand Down Expand Up @@ -161,7 +161,7 @@ class NormalizedLog(tf.keras.layers.Layer):

def build(self, input_shape: tf.Tensor) -> None:
self.squeeze_batch = lambda batch: batch
rank = input_shape.rank
rank = len(input_shape)
if rank == 4:
assert input_shape[1] == 1, "If the rank is 4, the second dimension must be length 1"
self.squeeze_batch = lambda batch: tf.squeeze(batch, axis=1)
Expand Down
5 changes: 3 additions & 2 deletions basic_pitch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from basic_pitch.layers import signal, nnaudio

tfkl = tf.keras.layers
keras_ops = tf.keras.ops if hasattr(tf.keras, "ops") else tf

MAX_N_SEMITONES = int(np.floor(12.0 * np.log2(0.5 * AUDIO_SAMPLE_RATE / ANNOTATIONS_BASE_FREQUENCY)))

Expand Down Expand Up @@ -184,7 +185,7 @@ def get_cqt(inputs: tf.Tensor, n_harmonics: int, use_batchnorm: bool) -> tf.Tens
bins_per_octave=12 * CONTOURS_BINS_PER_SEMITONE,
)(x)
x = signal.NormalizedLog()(x)
x = tf.expand_dims(x, -1)
x = keras_ops.expand_dims(x, -1)
if use_batchnorm:
x = tfkl.BatchNormalization()(x)
return x
Expand Down Expand Up @@ -263,7 +264,7 @@ def model(
x_contours = nn.FlattenFreqCh(name=contour_name)(x_contours) # contour output

# reduced contour output as input to notes
x_contours_reduced = tf.expand_dims(x_contours, -1)
x_contours_reduced = keras_ops.expand_dims(x_contours, -1)
else:
x_contours_reduced = x_contours

Expand Down
5 changes: 2 additions & 3 deletions basic_pitch/nn.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from typing import Any, List

import tensorflow as tf
import tensorflow.keras.backend as K

from basic_pitch.layers.math import log_base_b

Expand Down Expand Up @@ -97,7 +96,7 @@ class FlattenAudioCh(tf.keras.layers.Layer):

def call(self, x: tf.Tensor) -> tf.Tensor:
"""x: (batch, time, ch)"""
shapes = K.int_shape(x)
shapes = x.shape
tf.assert_equal(shapes[2], 1)
return tf.squeeze(x, axis=2)

Expand All @@ -111,7 +110,7 @@ class FlattenFreqCh(tf.keras.layers.Layer):
"""

def call(self, x: tf.Tensor) -> tf.Tensor:
shapes = K.int_shape(x)
shapes = x.shape
batch_size = tf.shape(x)[0]
time_dim = shapes[1]
freq_dim = shapes[2]
Expand Down
1 change: 0 additions & 1 deletion basic_pitch/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
)
from basic_pitch.inference import Model


os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"


Expand Down
1 change: 0 additions & 1 deletion basic_pitch/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ def main(
model.compile(
loss=loss,
optimizer=tf.keras.optimizers.Adam(learning_rate),
sample_weight_mode={"contour": None, "note": None, "onset": None},
)

logging.info("--- Model Training specs ---")
Expand Down
25 changes: 15 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,35 @@ version = "0.4.0"
description = "Basic Pitch, a lightweight yet powerful audio-to-MIDI converter with pitch bend detection."
readme = "README.md"
keywords = []
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Natural Language :: English",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
]
dependencies = [
"coremltools; platform_system == 'Darwin'",
"librosa>=0.8.0",
"librosa>=0.10.0",
"mir_eval>=0.6.0",
"numpy>=1.18",
"numpy>=1.21",
"onnxruntime; platform_system == 'Windows' and python_version < '3.11'",
"onnxruntime; platform_system == 'Windows' and python_version >= '3.12'",
"pretty_midi>=0.2.9",
"resampy>=0.2.2,<0.4.3",
"resampy>=0.2.2",
"scikit-learn",
"scipy>=1.4.1",
"tensorflow>=2.4.1,<2.15.1; platform_system != 'Darwin' and python_version >= '3.11'",
"tensorflow-macos>=2.4.1,<2.15.1; platform_system == 'Darwin' and python_version > '3.11'",
"scipy>=1.7.0",
"tensorflow>=2.4.1,<2.16.0; platform_system != 'Darwin' and python_version >= '3.11' and python_version < '3.12'",
"tensorflow>=2.16.0; platform_system != 'Darwin' and python_version >= '3.12'",
"tensorflow>=2.13.0; platform_system == 'Darwin' and python_version >= '3.12'",
"tflite-runtime; platform_system == 'Linux' and python_version < '3.11'",
"ai-edge-litert; platform_system != 'Windows' and python_version >= '3.12'",
"typing_extensions",
]

Expand Down Expand Up @@ -68,8 +71,10 @@ test = [
"mido"
]
tf = [
"tensorflow>=2.4.1,<2.15.1; platform_system != 'Darwin'",
"tensorflow-macos>=2.4.1,<2.15.1; platform_system == 'Darwin' and python_version > '3.7'",
"tensorflow>=2.4.1,<2.16.0; platform_system != 'Darwin' and python_version < '3.12'",
"tensorflow>=2.16.0; platform_system != 'Darwin' and python_version >= '3.12'",
"tensorflow>=2.13.0; platform_system == 'Darwin'",
"tensorboard>=2.4.1",
]
coreml = ["coremltools"]
onnx = ["onnxruntime"]
Expand Down
10 changes: 5 additions & 5 deletions tests/data/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
SLAKH_TEST_INDEX = json.load(open(RESOURCES_PATH / "data" / "slakh" / "dummy_index.json"))


@pytest.fixture # type: ignore[misc]
@pytest.fixture # type: ignore[untyped-decorator]
def mock_slakh_index() -> None: # type: ignore[misc]
with mock.patch("mirdata.datasets.slakh.Dataset.download"):
with mock.patch("mirdata.datasets.slakh.Dataset._index", new=SLAKH_TEST_INDEX):
yield


@pytest.fixture # type: ignore[misc]
@pytest.fixture # type: ignore[untyped-decorator]
def mock_medleydb_pitch_index() -> None: # type: ignore[misc]
with mock.patch("mirdata.datasets.medleydb_pitch.Dataset.download"):
with mock.patch("mirdata.datasets.medleydb_pitch.Dataset._index", new=MEDLEYDB_PITCH_TEST_INDEX):
yield


@pytest.fixture # type: ignore[misc]
@pytest.fixture # type: ignore[untyped-decorator]
def mock_maestro_index() -> None: # type: ignore[misc]
index_with_metadata = MAESTRO_TEST_INDEX
metadata = {mdata["midi_filename"].split(".")[0]: mdata for mdata in METADATA_TEST_INDEX}
Expand All @@ -36,14 +36,14 @@ def mock_maestro_index() -> None: # type: ignore[misc]
yield


@pytest.fixture # type: ignore[misc]
@pytest.fixture # type: ignore[untyped-decorator]
def mock_guitarset_index() -> None: # type: ignore[misc]
with mock.patch("mirdata.datasets.guitarset.Dataset.download"):
with mock.patch("mirdata.datasets.guitarset.Dataset._index", new=GUITAR_SET_TEST_INDEX):
yield


@pytest.fixture # type: ignore[misc]
@pytest.fixture # type: ignore[untyped-decorator]
def mock_ikala_index() -> None: # type: ignore[misc]
with mock.patch("mirdata.datasets.ikala.Dataset.download"):
with mock.patch("mirdata.datasets.ikala.Dataset._index", new=IKALA_TEST_INDEX):
Expand Down
1 change: 0 additions & 1 deletion tests/data/test_medleydb_pitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
create_input_data,
)


# TODO: Create test_medleydb_pitch_to_tf_example


Expand Down
2 changes: 1 addition & 1 deletion tests/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ def test_visualize_callback_on_epoch_end(tmpdir: str) -> None:
contours=True,
)

vc.model = MockModel()
vc.set_model(MockModel())

vc.on_epoch_end(1, {"loss": np.random.random(), "val_loss": np.random.random()})
2 changes: 1 addition & 1 deletion tests/test_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_predict() -> None:

# Check that model output has the expected length according to the last frame second computed downstream
# (via model_frames_to_time) with to a few frames of tolerance
audio_length_s = librosa.get_duration(filename=test_audio_path)
audio_length_s = librosa.get_duration(path=test_audio_path)
n_model_output_frames = model_output["note"].shape[0]
last_frame_s = note_creation.model_frames_to_time(n_model_output_frames)[-1]
np.testing.assert_allclose(last_frame_s, audio_length_s, atol=2 * ANNOTATION_HOP)
Expand Down
2 changes: 0 additions & 2 deletions tests/test_nn.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def test_defaults(self) -> None:
model.compile(
loss="binary_crossentropy",
optimizer=tf.keras.optimizers.Adam(0.1),
sample_weight_mode=None,
)

model.fit(
Expand Down Expand Up @@ -102,7 +101,6 @@ def test_fractions(self) -> None:
model.compile(
loss="binary_crossentropy",
optimizer=tf.keras.optimizers.Adam(0.1),
sample_weight_mode=None,
)

model.fit(
Expand Down
18 changes: 10 additions & 8 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py38,py39,py310,py311,full,data,manifest,check-formatting,lint,mypy
envlist = py310,py311,py312,full,data,manifest,check-formatting,lint,mypy
skipsdist = True
usedevelop = True
requires =
Expand All @@ -19,6 +19,7 @@ setenv =

# Tests with TF
[testenv:full]
basepython = python3.12
deps = -e .[dev]
commands =
pytest tests {posargs}
Expand All @@ -28,8 +29,9 @@ setenv =
LINE_LENGTH = "120"

[testenv:data]
basepython = python3.12
deps = -e .[data]
commands =
commands =
pytest tests/data {posargs}

[testenv:manifest]
Expand All @@ -38,27 +40,27 @@ skip_install = true
commands = check-manifest --ignore 'tests'

[testenv:check-formatting]
basepython = python3.10
basepython = python3.12
deps = black
skip_install = true
commands =
black {env:SOURCE} tests --line-length {env:LINE_LENGTH} --diff --check
black {env:SOURCE} tests --line-length {env:LINE_LENGTH} --target-version py312 --diff --check

[testenv:format]
basepython = python3.10
basepython = python3.12
deps = black
skip_install = true
commands =
black {env:SOURCE} tests --line-length {env:LINE_LENGTH}
black {env:SOURCE} tests --line-length {env:LINE_LENGTH} --target-version py312

[testenv:lint]
basepython = python3.10
basepython = python3.12
deps = flake8
skip_install = true
commands = flake8

[testenv:mypy]
basepython = python3.10
basepython = python3.12
deps =
mypy
commands = mypy basic_pitch tests --strict --ignore-missing-imports --allow-subclassing-any
Expand Down
Loading