diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py
index 3195b52c9..b13f2638a 100755
--- a/beets/dbcore/db.py
+++ b/beets/dbcore/db.py
@@ -189,7 +189,7 @@ class LazyConvertDict(object):
 
 class Model(object):
     """An abstract object representing an object in the database. Model
-    objects act like dictionaries (i.e., the allow subscript access like
+    objects act like dictionaries (i.e., they allow subscript access like
     ``obj['field']``). The same field set is available via attribute
     access as a shortcut (i.e., ``obj.field``). Three kinds of attributes are
     available:
diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py
index 521a5a1ee..5aa2b9812 100644
--- a/beets/dbcore/types.py
+++ b/beets/dbcore/types.py
@@ -131,6 +131,14 @@ class Integer(Type):
     query = query.NumericQuery
     model_type = int
 
+    def normalize(self, value):
+        try:
+            return self.model_type(round(float(value)))
+        except ValueError:
+            return self.null
+        except TypeError:
+            return self.null
+
 
 class PaddedInt(Integer):
     """An integer field that is formatted with a given number of digits,
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 7ef19871b..fca3e686f 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -166,6 +166,9 @@ Fixes:
 * Added a warning when configuration files defined in the `include` directive
   of the configuration file fail to be imported.
   :bug:`3498`
+* Added the normalize method to the dbcore.types.INTEGER class which now
+  properly returns integer values.
+  :bug:`762` and :bug:`3507`
 
 For plugin developers:
 
diff --git a/docs/plugins/chroma.rst b/docs/plugins/chroma.rst
index a6b60e6d8..1b86073b8 100644
--- a/docs/plugins/chroma.rst
+++ b/docs/plugins/chroma.rst
@@ -96,9 +96,7 @@ Usage
 
 Once you have all the dependencies sorted out, enable the ``chroma`` plugin in
 your configuration (see :ref:`using-plugins`) to benefit from fingerprinting
-the next time you run ``beet import``. (The plugin doesn't produce any obvious
-output by default. If you want to confirm that it's enabled, you can try
-running in verbose mode once with ``beet -v import``.)
+the next time you run ``beet import``.
 
 You can also use the ``beet fingerprint`` command to generate fingerprints for
 items already in your library. (Provide a query to fingerprint a subset of your

Integers are not normalized
## The Curse of the Floating BPM Value

### Problem

Beets currently allows the Model to set and the library to store any value for a field which is declared as `types.INTEGER` in the Item class. This behaviour however is not supported by MediaFile and for fields (such as bpm) which are [declared as integers](https://github.com/beetbox/mediafile/blob/master/mediafile.py#L1735)  these values are casted to integer before being written to the audio file tags. This results in the Model remaining always dirty and hence in the impossibility to have the library fully aligned with the media files.

This issue has been reported in #762 and discussed [here](https://discourse.beets.io/t/beet-write-bpm-quirk/738) on the forum. I am opening a new issue because even though this issue is most evident with the bpm field in reality it affects all fields declared `types.INTEGER` [in the Item class](https://github.com/beetbox/beets/blob/master/beets/library.py#L437).

In short, for any item in the library which has a bpm value in a non-integer format:
```text
     ...
     bpm: 119.434509277
     ...
```

Running this command:
```sh
$ beet write
```

will produce:

```
...
Madness - Full House: The Very Best of Madness - My Girl
  bpm: 119 -> 119.434509277
...
```

repeatedly. The audio file, if inspected through `beet info "My Girl"` will report:
```text
    ...
    bpm: 119
    ...
```
Therefore library and mediafile will remain forever out of sync.

### Setup

* OS: macOS 10.13.6
* Python version: 3.7.6
* beets version:  1.4.9 ([master](https://github.com/beetbox/beets/commit/8b8d223755662f499125b913ec9298faf3a2e3b2))
* Turning off plugins made problem go away (yes/no): no

My configuration (output of `beet config`) is:

```yaml
directory: /Volumes/Data/_TmpMusic_/
library: ~/.config/beets/test_library.db
asciify_paths: yes
id3v23: yes

ui:
    color: yes

plugins:
- the
- inline
- info
- fromfilename
- duplicates
- convert
- zero
- acousticbrainz

import:
    copy: no
    move: yes
    autotag: yes
    from_scratch: yes
    none_rec_action: asis
    duplicate_action: remove
    set_fields:
        albumstatus: new

paths:
    default: '%asciify{$initial_letter}/%the{%asciify{$albumartist}}/%asciify{$album}/$track. %asciify{$title}'
    singleton: V/Various Artists/%the{%asciify{$artist}}/$track. %asciify{$title}
    comp: V/Various Artists/%the{%asciify{$artist}}/$track. %asciify{$title}

musicbrainz:
    searchlimit: 3
item_fields:
    initial_letter: albumartist[0].upper() if albumartist else 'U'
zero:
    auto: yes
    fields:
    - month
    - day
    - encoder
    - lyrics
    - comments
    - artist_sort
    - original_month
    - original_day
    update_database: yes
    keep_fields: []
convert:
    command: ffmpeg -i $source -y -q:a 5 -f mp3 $dest
    extension: mp3
    max_bitrate: 182
    threads: 1
    auto: no
    dest: /Volumes/Data/_TmpMusic_/___converted___
    format: mp3
    formats:
        mp3: ffmpeg -i $source -y -q:a 5 -f mp3 $dest
        flac: ffmpeg -i $source -y -acodec flac -q:a 5 -f mp3 $dest
        aac:
            command: ffmpeg -i $source -y -vn -acodec aac -aq 1 $dest
            extension: m4a
        alac:
            command: ffmpeg -i $source -y -vn -acodec alac $dest
            extension: m4a
        opus: ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest
        ogg: ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest
        wma: ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest
    pretend: no
    link: no
    hardlink: no
    id3v23: inherit
    tmpdir:
    quiet: no
    embed: yes

    paths: {}
    no_convert: ''
    never_convert_lossy_files: no
    copy_album_art: no
    album_art_maxwidth: 0
acousticbrainz:
    auto: no
    force: no
    tags: [bpm]
duplicates:
    album: no
    checksum: ''
    copy: ''
    count: no
    delete: no
    format: ''
    full: no
    keys: []
    merge: no
    move: ''
    path: no
    tiebreak: {}
    strict: no
    tag: ''
the:
    the: yes
    a: yes
    format: '{0}, {1}'
    strip: no
    patterns: []
pathfields: {}
album_fields: {}

```

diff --git a/test/test_autotag.py b/test/test_autotag.py
index 28b7fd209..a11bc8fac 100644
--- a/test/test_autotag.py
+++ b/test/test_autotag.py
@@ -91,7 +91,10 @@ class PluralityTest(_common.TestCase):
                  for i in range(5)]
         likelies, _ = match.current_metadata(items)
         for f in fields:
-            self.assertEqual(likelies[f], '%s_1' % f)
+            if isinstance(likelies[f], int):
+                self.assertEqual(likelies[f], 0)
+            else:
+                self.assertEqual(likelies[f], '%s_1' % f)
 
 
 def _make_item(title, track, artist=u'some artist'):
diff --git a/test/test_dbcore.py b/test/test_dbcore.py
index 9bf78de67..0d40896da 100644
--- a/test/test_dbcore.py
+++ b/test/test_dbcore.py
@@ -53,6 +53,7 @@ class ModelFixture1(dbcore.Model):
     _fields = {
         'id': dbcore.types.PRIMARY_ID,
         'field_one': dbcore.types.INTEGER,
+        'field_two': dbcore.types.STRING,
     }
     _types = {
         'some_float_field': dbcore.types.FLOAT,
@@ -355,7 +356,7 @@ class ModelTest(unittest.TestCase):
     def test_items(self):
         model = ModelFixture1(self.db)
         model.id = 5
-        self.assertEqual({('id', 5), ('field_one', 0)},
+        self.assertEqual({('id', 5), ('field_one', 0), ('field_two', '')},
                          set(model.items()))
 
     def test_delete_internal_field(self):
@@ -370,10 +371,28 @@ class ModelTest(unittest.TestCase):
 
 
 class FormatTest(unittest.TestCase):
-    def test_format_fixed_field(self):
+    def test_format_fixed_field_integer(self):
         model = ModelFixture1()
-        model.field_one = u'caf\xe9'
+        model.field_one = 155
         value = model.formatted().get('field_one')
+        self.assertEqual(value, u'155')
+
+    def test_format_fixed_field_integer_normalized(self):
+        """The normalize method of the Integer class rounds floats
+        """
+        model = ModelFixture1()
+        model.field_one = 142.432
+        value = model.formatted().get('field_one')
+        self.assertEqual(value, u'142')
+
+        model.field_one = 142.863
+        value = model.formatted().get('field_one')
+        self.assertEqual(value, u'143')
+
+    def test_format_fixed_field_string(self):
+        model = ModelFixture1()
+        model.field_two = u'caf\xe9'
+        value = model.formatted().get('field_two')
         self.assertEqual(value, u'caf\xe9')
 
     def test_format_flex_field(self):

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest nose coverage
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/beetbox/beets.git@545c65d903e38d37fd2c1734ec69eac609bea035#egg=beets
confuse==2.0.1
coverage==7.8.0
exceptiongroup==1.2.2
filetype==1.2.0
iniconfig==2.1.0
jellyfish==1.1.3
mediafile==0.13.0
munkres==1.1.4
musicbrainzngs==0.7.1
mutagen==1.47.0
nose==1.3.7
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
PyYAML==6.0.2
six==1.17.0
tomli==2.2.1
Unidecode==1.3.8

name: beets
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - confuse==2.0.1
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - filetype==1.2.0
      - iniconfig==2.1.0
      - jellyfish==1.1.3
      - mediafile==0.13.0
      - munkres==1.1.4
      - musicbrainzngs==0.7.1
      - mutagen==1.47.0
      - nose==1.3.7
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pyyaml==6.0.2
      - six==1.17.0
      - tomli==2.2.1
      - unidecode==1.3.8
prefix: /opt/conda/envs/beets

test/test_dbcore.py::FormatTest::test_format_fixed_field_integer_normalized
test/test_autotag.py::PluralityTest::test_albumartist_consensus test/test_autotag.py::PluralityTest::test_current_metadata_artist_consensus test/test_autotag.py::PluralityTest::test_current_metadata_finds_pluralities test/test_autotag.py::PluralityTest::test_current_metadata_likelies test/test_autotag.py::PluralityTest::test_plurality_conflict test/test_autotag.py::PluralityTest::test_plurality_consensus test/test_autotag.py::PluralityTest::test_plurality_empty_sequence_raises_error test/test_autotag.py::PluralityTest::test_plurality_near_consensus test/test_autotag.py::DistanceTest::test_add test/test_autotag.py::DistanceTest::test_add_equality test/test_autotag.py::DistanceTest::test_add_expr test/test_autotag.py::DistanceTest::test_add_number test/test_autotag.py::DistanceTest::test_add_priority test/test_autotag.py::DistanceTest::test_add_ratio test/test_autotag.py::DistanceTest::test_add_string test/test_autotag.py::DistanceTest::test_add_string_both_none test/test_autotag.py::DistanceTest::test_add_string_none test/test_autotag.py::DistanceTest::test_distance test/test_autotag.py::DistanceTest::test_items test/test_autotag.py::DistanceTest::test_max_distance test/test_autotag.py::DistanceTest::test_operators test/test_autotag.py::DistanceTest::test_raw_distance test/test_autotag.py::DistanceTest::test_update test/test_autotag.py::TrackDistanceTest::test_different_artist test/test_autotag.py::TrackDistanceTest::test_different_title test/test_autotag.py::TrackDistanceTest::test_identical_tracks test/test_autotag.py::TrackDistanceTest::test_various_artists_tolerated test/test_autotag.py::AlbumDistanceTest::test_comp_no_track_artists test/test_autotag.py::AlbumDistanceTest::test_comp_track_artists_do_not_match test/test_autotag.py::AlbumDistanceTest::test_comp_track_artists_match test/test_autotag.py::AlbumDistanceTest::test_global_artists_differ test/test_autotag.py::AlbumDistanceTest::test_identical_albums test/test_autotag.py::AlbumDistanceTest::test_incomplete_album test/test_autotag.py::AlbumDistanceTest::test_per_medium_track_numbers test/test_autotag.py::AlbumDistanceTest::test_tracks_out_of_order test/test_autotag.py::AlbumDistanceTest::test_two_medium_release test/test_autotag.py::AssignmentTest::test_order_works_when_track_names_are_entirely_wrong test/test_autotag.py::AssignmentTest::test_order_works_with_extra_tracks test/test_autotag.py::AssignmentTest::test_order_works_with_invalid_track_numbers test/test_autotag.py::AssignmentTest::test_order_works_with_missing_tracks test/test_autotag.py::AssignmentTest::test_reorder_when_track_numbers_incorrect test/test_autotag.py::ApplyTest::test_album_and_artist_applied_to_all test/test_autotag.py::ApplyTest::test_album_artist_overridden_by_nonempty_track_artist test/test_autotag.py::ApplyTest::test_album_artist_overrides_empty_track_artist test/test_autotag.py::ApplyTest::test_albumtype_applied test/test_autotag.py::ApplyTest::test_artist_credit test/test_autotag.py::ApplyTest::test_artist_credit_applied test/test_autotag.py::ApplyTest::test_artist_credit_falls_back_to_albumartist test/test_autotag.py::ApplyTest::test_artist_credit_prefers_artist_over_albumartist_credit test/test_autotag.py::ApplyTest::test_artist_sort_applied test/test_autotag.py::ApplyTest::test_data_source_applied test/test_autotag.py::ApplyTest::test_date_only_zeros_month_and_day test/test_autotag.py::ApplyTest::test_disc_index_applied test/test_autotag.py::ApplyTest::test_disc_total_applied test/test_autotag.py::ApplyTest::test_full_date_applied test/test_autotag.py::ApplyTest::test_mb_albumid_and_artistid_applied test/test_autotag.py::ApplyTest::test_mb_trackid_applied test/test_autotag.py::ApplyTest::test_missing_date_applies_nothing test/test_autotag.py::ApplyTest::test_per_disc_numbering test/test_autotag.py::ApplyTest::test_per_disc_numbering_track_total test/test_autotag.py::ApplyTest::test_titles_applied test/test_autotag.py::ApplyTest::test_track_index_applied test/test_autotag.py::ApplyTest::test_track_total_applied test/test_autotag.py::ApplyCompilationTest::test_album_and_track_artists_separate test/test_autotag.py::ApplyCompilationTest::test_mb_albumartistid_applied test/test_autotag.py::ApplyCompilationTest::test_va_flag_cleared_does_not_set_comp test/test_autotag.py::ApplyCompilationTest::test_va_flag_sets_comp test/test_autotag.py::StringDistanceTest::test_accented_characters test/test_autotag.py::StringDistanceTest::test_ampersand_expansion test/test_autotag.py::StringDistanceTest::test_brackets_have_lower_weight test/test_autotag.py::StringDistanceTest::test_case_ignored test/test_autotag.py::StringDistanceTest::test_different_strings test/test_autotag.py::StringDistanceTest::test_empty_strings test/test_autotag.py::StringDistanceTest::test_ep_label_has_zero_weight test/test_autotag.py::StringDistanceTest::test_equal_strings test/test_autotag.py::StringDistanceTest::test_featured_has_lower_weight test/test_autotag.py::StringDistanceTest::test_heuristic_does_not_harm_distance test/test_autotag.py::StringDistanceTest::test_leading_the_has_lower_weight test/test_autotag.py::StringDistanceTest::test_parens_have_lower_weight test/test_autotag.py::StringDistanceTest::test_postfix_a test/test_autotag.py::StringDistanceTest::test_postfix_an test/test_autotag.py::StringDistanceTest::test_postfix_the test/test_autotag.py::StringDistanceTest::test_punctuation_ignored test/test_autotag.py::StringDistanceTest::test_solo_pattern test/test_autotag.py::EnumTest::test_ordered_enum test/test_dbcore.py::MigrationTest::test_extra_model_adds_table test/test_dbcore.py::MigrationTest::test_open_with_fewer_fields_leaves_untouched test/test_dbcore.py::MigrationTest::test_open_with_multiple_new_fields test/test_dbcore.py::MigrationTest::test_open_with_new_field_adds_column test/test_dbcore.py::MigrationTest::test_open_with_same_fields_leaves_untouched test/test_dbcore.py::ModelTest::test_add_model test/test_dbcore.py::ModelTest::test_check_db_fails test/test_dbcore.py::ModelTest::test_computed_field test/test_dbcore.py::ModelTest::test_delete_fixed_attribute test/test_dbcore.py::ModelTest::test_delete_flexattr test/test_dbcore.py::ModelTest::test_delete_flexattr_persists test/test_dbcore.py::ModelTest::test_delete_flexattr_via_dot test/test_dbcore.py::ModelTest::test_delete_internal_field test/test_dbcore.py::ModelTest::test_delete_non_existent_attribute test/test_dbcore.py::ModelTest::test_items test/test_dbcore.py::ModelTest::test_load_deleted_flex_field test/test_dbcore.py::ModelTest::test_missing_field test/test_dbcore.py::ModelTest::test_normalization_for_typed_flex_fields test/test_dbcore.py::ModelTest::test_null_value_normalization_by_type test/test_dbcore.py::ModelTest::test_null_value_stays_none_for_untyped_field test/test_dbcore.py::ModelTest::test_parse_nonstring test/test_dbcore.py::ModelTest::test_retrieve_by_id test/test_dbcore.py::ModelTest::test_store_and_retrieve_flexattr test/test_dbcore.py::ModelTest::test_store_fixed_field test/test_dbcore.py::FormatTest::test_format_fixed_field_integer test/test_dbcore.py::FormatTest::test_format_fixed_field_string test/test_dbcore.py::FormatTest::test_format_flex_field test/test_dbcore.py::FormatTest::test_format_flex_field_bytes test/test_dbcore.py::FormatTest::test_format_typed_flex_field test/test_dbcore.py::FormatTest::test_format_unset_field test/test_dbcore.py::FormattedMappingTest::test_get_method_with_default test/test_dbcore.py::FormattedMappingTest::test_get_method_with_specified_default test/test_dbcore.py::FormattedMappingTest::test_get_unset_field test/test_dbcore.py::FormattedMappingTest::test_keys_equal_model_keys test/test_dbcore.py::ParseTest::test_parse_fixed_field test/test_dbcore.py::ParseTest::test_parse_flex_field test/test_dbcore.py::ParseTest::test_parse_untyped_field test/test_dbcore.py::QueryParseTest::test_colon_at_end test/test_dbcore.py::QueryParseTest::test_empty_query_part test/test_dbcore.py::QueryParseTest::test_escaped_colon test/test_dbcore.py::QueryParseTest::test_escaped_colon_in_regexp test/test_dbcore.py::QueryParseTest::test_keyed_regexp test/test_dbcore.py::QueryParseTest::test_multiple_years test/test_dbcore.py::QueryParseTest::test_one_basic_regexp test/test_dbcore.py::QueryParseTest::test_one_basic_term test/test_dbcore.py::QueryParseTest::test_one_keyed_term test/test_dbcore.py::QueryParseTest::test_single_year test/test_dbcore.py::QueryFromStringsTest::test_empty_query_part test/test_dbcore.py::QueryFromStringsTest::test_parse_fixed_type_query test/test_dbcore.py::QueryFromStringsTest::test_parse_flex_type_query test/test_dbcore.py::QueryFromStringsTest::test_parse_named_query test/test_dbcore.py::QueryFromStringsTest::test_two_parts test/test_dbcore.py::QueryFromStringsTest::test_zero_parts test/test_dbcore.py::SortFromStringsTest::test_fixed_field_sort test/test_dbcore.py::SortFromStringsTest::test_flex_field_sort test/test_dbcore.py::SortFromStringsTest::test_one_parts test/test_dbcore.py::SortFromStringsTest::test_special_sort test/test_dbcore.py::SortFromStringsTest::test_two_parts test/test_dbcore.py::SortFromStringsTest::test_zero_parts test/test_dbcore.py::ParseSortedQueryTest::test_and_query test/test_dbcore.py::ParseSortedQueryTest::test_leading_comma_or_query test/test_dbcore.py::ParseSortedQueryTest::test_no_space_before_comma_or_query test/test_dbcore.py::ParseSortedQueryTest::test_no_spaces_or_query test/test_dbcore.py::ParseSortedQueryTest::test_only_direction test/test_dbcore.py::ParseSortedQueryTest::test_or_query test/test_dbcore.py::ParseSortedQueryTest::test_trailing_comma_or_query test/test_dbcore.py::ResultsIteratorTest::test_concurrent_iterators test/test_dbcore.py::ResultsIteratorTest::test_iterate_once test/test_dbcore.py::ResultsIteratorTest::test_iterate_slow_sort test/test_dbcore.py::ResultsIteratorTest::test_iterate_twice test/test_dbcore.py::ResultsIteratorTest::test_length test/test_dbcore.py::ResultsIteratorTest::test_no_results test/test_dbcore.py::ResultsIteratorTest::test_out_of_range test/test_dbcore.py::ResultsIteratorTest::test_slow_query test/test_dbcore.py::ResultsIteratorTest::test_slow_query_negative test/test_dbcore.py::ResultsIteratorTest::test_slow_sort_subscript test/test_dbcore.py::ResultsIteratorTest::test_unsorted_subscript
docs/plugins/chroma.rst docs/changelog.rst beets/dbcore/db.py beets/dbcore/types.py
docs/plugins/chroma.rst docs/changelog.rst beets/dbcore/db.py beets/dbcore/types.py