@@ -26,6 +26,7 @@ import com.adamratzman.spotify.utils.Market
2626import com.adamratzman.spotify.utils.formatDate
2727import kotlinx.serialization.list
2828import kotlinx.serialization.serializer
29+ import kotlin.reflect.KClass
2930
3031typealias BrowseAPI = BrowseApi
3132
@@ -41,9 +42,9 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
4142 fun getAvailableGenreSeeds (): SpotifyRestAction <List <String >> {
4243 return toAction {
4344 get(EndpointBuilder (" /recommendations/available-genre-seeds" ).toString()).toInnerArray(
44- String .serializer().list,
45- " genres" ,
46- json
45+ String .serializer().list,
46+ " genres" ,
47+ json
4748 )
4849 }
4950 }
@@ -66,10 +67,10 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
6667 ): SpotifyRestActionPaging <SimpleAlbum , PagingObject <SimpleAlbum >> {
6768 return toActionPaging {
6869 get(
69- EndpointBuilder (" /browse/new-releases" ).with (" limit" , limit).with (" offset" , offset).with (
70- " country" ,
71- market?.name
72- ).toString()
70+ EndpointBuilder (" /browse/new-releases" ).with (" limit" , limit).with (" offset" , offset).with (
71+ " country" ,
72+ market?.name
73+ ).toString()
7374 ).toPagingObject(SimpleAlbum .serializer(), " albums" , endpoint = this , json = json)
7475 }
7576 }
@@ -101,12 +102,12 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
101102 ): SpotifyRestAction <FeaturedPlaylists > {
102103 return toAction {
103104 get(
104- EndpointBuilder (" /browse/featured-playlists" ).with (" limit" , limit).with (" offset" , offset).with (
105- " market" ,
106- market?.name
107- ).with (" locale" , locale).with (" timestamp" , timestamp?.let {
108- formatDate(" yyyy-MM-dd'T'HH:mm:ss" , it)
109- }).toString()
105+ EndpointBuilder (" /browse/featured-playlists" ).with (" limit" , limit).with (" offset" , offset).with (
106+ " market" ,
107+ market?.name
108+ ).with (" locale" , locale).with (" timestamp" , timestamp?.let {
109+ formatDate(" yyyy-MM-dd'T'HH:mm:ss" , it)
110+ }).toString()
110111 ).toObject(FeaturedPlaylists .serializer(), api, json)
111112 }
112113 }
@@ -134,10 +135,10 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
134135 ): SpotifyRestActionPaging <SpotifyCategory , PagingObject <SpotifyCategory >> {
135136 return toActionPaging {
136137 get(
137- EndpointBuilder (" /browse/categories" ).with (" limit" , limit).with (" offset" , offset).with (
138- " market" ,
139- market?.name
140- ).with (" locale" , locale).toString()
138+ EndpointBuilder (" /browse/categories" ).with (" limit" , limit).with (" offset" , offset).with (
139+ " market" ,
140+ market?.name
141+ ).with (" locale" , locale).toString()
141142 ).toPagingObject(SpotifyCategory .serializer(), " categories" , endpoint = this , json = json)
142143 }
143144 }
@@ -162,8 +163,8 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
162163 ): SpotifyRestAction <SpotifyCategory > {
163164 return toAction {
164165 get(
165- EndpointBuilder (" /browse/categories/${categoryId.encodeUrl()} " ).with (" market" , market?.name)
166- .with (" locale" , locale).toString()
166+ EndpointBuilder (" /browse/categories/${categoryId.encodeUrl()} " ).with (" market" , market?.name)
167+ .with (" locale" , locale).toString()
167168 ).toObject(SpotifyCategory .serializer(), api, json)
168169 }
169170 }
@@ -187,11 +188,11 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
187188 ): SpotifyRestActionPaging <SimplePlaylist , PagingObject <SimplePlaylist >> {
188189 return toActionPaging {
189190 get(
190- EndpointBuilder (" /browse/categories/${categoryId.encodeUrl()} /playlists" ).with (
191- " limit" ,
192- limit
193- ).with (" offset" , offset)
194- .with (" market" , market?.name).toString()
191+ EndpointBuilder (" /browse/categories/${categoryId.encodeUrl()} /playlists" ).with (
192+ " limit" ,
193+ limit
194+ ).with (" offset" , offset)
195+ .with (" market" , market?.name).toString()
195196 ).toPagingObject(SimplePlaylist .serializer(), " playlists" , endpoint = this , json = json)
196197 }
197198 }
@@ -237,16 +238,16 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
237238 minAttributes : List <TrackAttribute <* >> = listOf(),
238239 maxAttributes : List <TrackAttribute <* >> = listOf()
239240 ): SpotifyRestAction <RecommendationResponse > =
240- getRecommendations(
241- seedArtists,
242- seedGenres,
243- seedTracks,
244- limit,
245- market,
246- targetAttributes.map { it.tuneableTrackAttribute to it.value }.toMap(),
247- minAttributes.map { it.tuneableTrackAttribute to it.value }.toMap(),
248- maxAttributes.map { it.tuneableTrackAttribute to it.value }.toMap()
249- )
241+ getRecommendations(
242+ seedArtists,
243+ seedGenres,
244+ seedTracks,
245+ limit,
246+ market,
247+ targetAttributes.map { it.tuneableTrackAttribute to it.value }.toMap(),
248+ minAttributes.map { it.tuneableTrackAttribute to it.value }.toMap(),
249+ maxAttributes.map { it.tuneableTrackAttribute to it.value }.toMap()
250+ )
250251
251252 /* *
252253 * Create a playlist-style listening experience based on seed artists, tracks and genres.
@@ -292,18 +293,18 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
292293 ): SpotifyRestAction <RecommendationResponse > {
293294 if (seedArtists?.isEmpty() != false && seedGenres?.isEmpty() != false && seedTracks?.isEmpty() != false ) {
294295 throw SpotifyException .BadRequestException (
295- ErrorObject (
296- 400 ,
297- " At least one seed (genre, artist, track) must be provided."
298- )
296+ ErrorObject (
297+ 400 ,
298+ " At least one seed (genre, artist, track) must be provided."
299+ )
299300 )
300301 }
301302
302303 return toAction {
303304 val builder = EndpointBuilder (" /recommendations" ).with (" limit" , limit).with (" market" , market?.name)
304- .with (" seed_artists" , seedArtists?.joinToString(" ," ) { ArtistUri (it).id.encodeUrl() })
305- .with (" seed_genres" , seedGenres?.joinToString(" ," ) { it.encodeUrl() })
306- .with (" seed_tracks" , seedTracks?.joinToString(" ," ) { TrackUri (it).id.encodeUrl() })
305+ .with (" seed_artists" , seedArtists?.joinToString(" ," ) { ArtistUri (it).id.encodeUrl() })
306+ .with (" seed_genres" , seedGenres?.joinToString(" ," ) { it.encodeUrl() })
307+ .with (" seed_tracks" , seedTracks?.joinToString(" ," ) { TrackUri (it).id.encodeUrl() })
307308 targetAttributes.forEach { (attribute, value) -> builder.with (" target_$attribute " , value) }
308309 minAttributes.forEach { (attribute, value) -> builder.with (" min_$attribute " , value) }
309310 maxAttributes.forEach { (attribute, value) -> builder.with (" max_$attribute " , value) }
@@ -321,33 +322,34 @@ sealed class TuneableTrackAttribute<T : Number>(
321322 val attribute : String ,
322323 val integerOnly : Boolean ,
323324 val min : T ? ,
324- val max : T ?
325+ val max : T ? ,
326+ val tClazz : KClass <T >
325327) {
326328 /* *
327329 * A confidence measure from 0.0 to 1.0 of whether the track is acoustic.
328330 * 1.0 represents high confidence the track is acoustic.
329331 */
330- object ACOUSTICNESS : TuneableTrackAttribute<Float>(" acousticness" , false , 0f , 1f )
332+ object ACOUSTICNESS : TuneableTrackAttribute<Float>(" acousticness" , false , 0f , 1f , Float : :class )
331333
332334 /* *
333335 * Danceability describes how suitable a track is for dancing based on a combination of musical
334336 * elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is
335337 * least danceable and 1.0 is most danceable.
336338 */
337- object DANCEABILITY : TuneableTrackAttribute<Float>(" danceability" , false , 0f , 1f )
339+ object DANCEABILITY : TuneableTrackAttribute<Float>(" danceability" , false , 0f , 1f , Float : :class )
338340
339341 /* *
340342 * The duration of the track in milliseconds.
341343 */
342- object DURATION_IN_MILLISECONDS : TuneableTrackAttribute<Int>(" duration_ms" , true , 0 , null )
344+ object DURATION_IN_MILLISECONDS : TuneableTrackAttribute<Int>(" duration_ms" , true , 0 , null , Int : :class )
343345
344346 /* *
345347 * Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity.
346348 * Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy,
347349 * while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute
348350 * include dynamic range, perceived loudness, timbre, onset rate, and general entropy.
349351 */
350- object ENERGY : TuneableTrackAttribute<Float>(" energy" , false , 0f , 1f )
352+ object ENERGY : TuneableTrackAttribute<Float>(" energy" , false , 0f , 1f , Float : :class )
351353
352354 /* *
353355 * Predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as
@@ -356,34 +358,34 @@ sealed class TuneableTrackAttribute<T : Number>(
356358 * no vocal content. Values above 0.5 are intended to represent instrumental tracks, but
357359 * confidence is higher as the value approaches 1.0.
358360 */
359- object INSTRUMENTALNESS : TuneableTrackAttribute<Float>(" instrumentalness" , false , 0f , 1f )
361+ object INSTRUMENTALNESS : TuneableTrackAttribute<Float>(" instrumentalness" , false , 0f , 1f , Float : :class )
360362
361363 /* *
362364 * The key the track is in. Integers map to pitches using standard Pitch Class notation.
363365 * E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on.
364366 */
365- object KEY : TuneableTrackAttribute<Int>(" key" , true , 0 , 11 )
367+ object KEY : TuneableTrackAttribute<Int>(" key" , true , 0 , 11 , Int : :class )
366368
367369 /* *
368370 * Detects the presence of an audience in the recording. Higher liveness values represent an increased
369371 * probability that the track was performed live. A value above 0.8 provides strong likelihood
370372 * that the track is live.
371373 */
372- object LIVENESS : TuneableTrackAttribute<Float>(" liveness" , false , 0f , 1f )
374+ object LIVENESS : TuneableTrackAttribute<Float>(" liveness" , false , 0f , 1f , Float : :class )
373375
374376 /* *
375377 * The overall loudness of a track in decibels (dB). Loudness values are averaged across the
376378 * entire track and are useful for comparing relative loudness of tracks. Loudness is the
377379 * quality of a sound that is the primary psychological correlate of physical strength (amplitude).
378380 * Values typically range between -60 and 0 db.
379381 */
380- object LOUDNESS : TuneableTrackAttribute<Float>(" loudness" , false , null , null )
382+ object LOUDNESS : TuneableTrackAttribute<Float>(" loudness" , false , null , null , Float : :class )
381383
382384 /* *
383385 * Mode indicates the modality (major or minor) of a track, the type of scale from which its
384386 * melodic content is derived. Major is represented by 1 and minor is 0.
385387 */
386- object MODE : TuneableTrackAttribute<Int>(" mode" , true , 0 , 1 )
388+ object MODE : TuneableTrackAttribute<Int>(" mode" , true , 0 , 1 , Int : :class )
387389
388390 /* *
389391 * The popularity of the track. The value will be between 0 and 100, with 100 being the most popular.
@@ -392,7 +394,7 @@ sealed class TuneableTrackAttribute<T : Number>(
392394 * the market parameter, it is expected to find relinked tracks with popularities that do not match
393395 * min_*, max_*and target_* popularities. These relinked tracks are accurate replacements for unplayable tracks with the expected popularity scores. Original, non-relinked tracks are available via the linked_from attribute of the relinked track response.
394396 */
395- object POPULARITY : TuneableTrackAttribute<Int>(" popularity" , true , 0 , 100 )
397+ object POPULARITY : TuneableTrackAttribute<Int>(" popularity" , true , 0 , 100 , Int : :class )
396398
397399 /* *
398400 * Speechiness detects the presence of spoken words in a track. The more exclusively speech-like the
@@ -402,61 +404,67 @@ sealed class TuneableTrackAttribute<T : Number>(
402404 * such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like
403405 * tracks.
404406 */
405- object SPEECHINESS : TuneableTrackAttribute<Float>(" speechiness" , false , 0f , 1f )
407+ object SPEECHINESS : TuneableTrackAttribute<Float>(" speechiness" , false , 0f , 1f , Float : :class )
406408
407409 /* *
408410 * The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the
409411 * speed or pace of a given piece and derives directly from the average beat duration.
410412 */
411- object TEMPO : TuneableTrackAttribute<Float>(" tempo" , false , 0f , null )
413+ object TEMPO : TuneableTrackAttribute<Float>(" tempo" , false , 0f , null , Float : :class )
412414
413415 /* *
414416 * An estimated overall time signature of a track. The time signature (meter)
415417 * is a notational convention to specify how many beats are in each bar (or measure).
416418 * The time signature ranges from 3 to 7 indicating time signatures of 3/4, to 7/4.
417419 * A value of -1 may indicate no time signature, while a value of 1 indicates a rather complex or changing time signature.
418420 */
419- object TIME_SIGNATURE : TuneableTrackAttribute<Int>(" time_signature" , true , -1 , 7 )
421+ object TIME_SIGNATURE : TuneableTrackAttribute<Int>(" time_signature" , true , -1 , 7 , Int : :class )
420422
421423 /* *
422424 * A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high
423425 * valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence
424426 * sound more negative (e.g. sad, depressed, angry).
425427 */
426- object VALENCE : TuneableTrackAttribute<Float>(" valence" , false , 0f , 1f )
428+ object VALENCE : TuneableTrackAttribute<Float>(" valence" , false , 0f , 1f , Float : :class )
427429
428430 override fun toString () = attribute
429431
430- fun asTrackAttribute (value : T ): TrackAttribute <T > {
432+ fun < V : Number > asTrackAttribute (value : V ): TrackAttribute <T > {
431433 require(! (min != null && min.toDouble() > value.toDouble())) { " Attribute value for $this must be greater than $min !" }
432434 require(! (max != null && max.toDouble() < value.toDouble())) { " Attribute value for $this must be less than $max !" }
433435
434- return TrackAttribute (this , value)
436+ @Suppress(" UNCHECKED_CAST" )
437+ return TrackAttribute (this , when (tClazz) {
438+ Int ::class -> value.toInt() as T
439+ Float ::class -> value.toFloat() as T
440+ Double ::class -> value.toDouble() as T
441+ else -> value.toDouble() as T
442+ })
435443 }
436444
437445 companion object {
438446 fun values () = listOf (
439- ACOUSTICNESS ,
440- DANCEABILITY ,
441- DURATION_IN_MILLISECONDS ,
442- ENERGY ,
443- INSTRUMENTALNESS ,
444- KEY ,
445- LIVENESS ,
446- LOUDNESS ,
447- MODE ,
448- POPULARITY ,
449- SPEECHINESS ,
450- TEMPO ,
451- TIME_SIGNATURE ,
452- VALENCE
447+ ACOUSTICNESS ,
448+ DANCEABILITY ,
449+ DURATION_IN_MILLISECONDS ,
450+ ENERGY ,
451+ INSTRUMENTALNESS ,
452+ KEY ,
453+ LIVENESS ,
454+ LOUDNESS ,
455+ MODE ,
456+ POPULARITY ,
457+ SPEECHINESS ,
458+ TEMPO ,
459+ TIME_SIGNATURE ,
460+ VALENCE
453461 )
454462 }
455463}
456464
457465data class TrackAttribute <T : Number >(val tuneableTrackAttribute : TuneableTrackAttribute <T >, val value : T ) {
458466 companion object {
459467 fun <T : Number > create (tuneableTrackAttribute : TuneableTrackAttribute <T >, value : T ) =
460- tuneableTrackAttribute.asTrackAttribute(value)
468+ tuneableTrackAttribute.asTrackAttribute(value)
461469 }
462470}
0 commit comments