@@ -24,6 +24,7 @@ import com.adamratzman.spotify.models.serialization.toObject
24
24
import com.adamratzman.spotify.models.serialization.toPagingObject
25
25
import com.adamratzman.spotify.utils.Market
26
26
import com.adamratzman.spotify.utils.formatDate
27
+ import kotlin.reflect.KClass
27
28
import kotlinx.serialization.list
28
29
import kotlinx.serialization.serializer
29
30
@@ -41,9 +42,9 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
41
42
fun getAvailableGenreSeeds (): SpotifyRestAction <List <String >> {
42
43
return toAction {
43
44
get(EndpointBuilder (" /recommendations/available-genre-seeds" ).toString()).toInnerArray(
44
- String .serializer().list,
45
- " genres" ,
46
- json
45
+ String .serializer().list,
46
+ " genres" ,
47
+ json
47
48
)
48
49
}
49
50
}
@@ -66,10 +67,10 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
66
67
): SpotifyRestActionPaging <SimpleAlbum , PagingObject <SimpleAlbum >> {
67
68
return toActionPaging {
68
69
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()
73
74
).toPagingObject(SimpleAlbum .serializer(), " albums" , endpoint = this , json = json)
74
75
}
75
76
}
@@ -101,12 +102,12 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
101
102
): SpotifyRestAction <FeaturedPlaylists > {
102
103
return toAction {
103
104
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()
110
111
).toObject(FeaturedPlaylists .serializer(), api, json)
111
112
}
112
113
}
@@ -134,10 +135,10 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
134
135
): SpotifyRestActionPaging <SpotifyCategory , PagingObject <SpotifyCategory >> {
135
136
return toActionPaging {
136
137
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()
141
142
).toPagingObject(SpotifyCategory .serializer(), " categories" , endpoint = this , json = json)
142
143
}
143
144
}
@@ -162,8 +163,8 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
162
163
): SpotifyRestAction <SpotifyCategory > {
163
164
return toAction {
164
165
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()
167
168
).toObject(SpotifyCategory .serializer(), api, json)
168
169
}
169
170
}
@@ -187,11 +188,11 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
187
188
): SpotifyRestActionPaging <SimplePlaylist , PagingObject <SimplePlaylist >> {
188
189
return toActionPaging {
189
190
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()
195
196
).toPagingObject(SimplePlaylist .serializer(), " playlists" , endpoint = this , json = json)
196
197
}
197
198
}
@@ -237,16 +238,16 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
237
238
minAttributes : List <TrackAttribute <* >> = listOf(),
238
239
maxAttributes : List <TrackAttribute <* >> = listOf()
239
240
): 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
+ )
250
251
251
252
/* *
252
253
* Create a playlist-style listening experience based on seed artists, tracks and genres.
@@ -292,18 +293,18 @@ class BrowseApi(api: SpotifyApi<*, *>) : SpotifyEndpoint(api) {
292
293
): SpotifyRestAction <RecommendationResponse > {
293
294
if (seedArtists?.isEmpty() != false && seedGenres?.isEmpty() != false && seedTracks?.isEmpty() != false ) {
294
295
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
+ )
299
300
)
300
301
}
301
302
302
303
return toAction {
303
304
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() })
307
308
targetAttributes.forEach { (attribute, value) -> builder.with (" target_$attribute " , value) }
308
309
minAttributes.forEach { (attribute, value) -> builder.with (" min_$attribute " , value) }
309
310
maxAttributes.forEach { (attribute, value) -> builder.with (" max_$attribute " , value) }
@@ -321,33 +322,34 @@ sealed class TuneableTrackAttribute<T : Number>(
321
322
val attribute : String ,
322
323
val integerOnly : Boolean ,
323
324
val min : T ? ,
324
- val max : T ?
325
+ val max : T ? ,
326
+ val tClazz : KClass <T >
325
327
) {
326
328
/* *
327
329
* A confidence measure from 0.0 to 1.0 of whether the track is acoustic.
328
330
* 1.0 represents high confidence the track is acoustic.
329
331
*/
330
- object ACOUSTICNESS : TuneableTrackAttribute<Float>(" acousticness" , false , 0f , 1f )
332
+ object ACOUSTICNESS : TuneableTrackAttribute<Float>(" acousticness" , false , 0f , 1f , Float : :class )
331
333
332
334
/* *
333
335
* Danceability describes how suitable a track is for dancing based on a combination of musical
334
336
* elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is
335
337
* least danceable and 1.0 is most danceable.
336
338
*/
337
- object DANCEABILITY : TuneableTrackAttribute<Float>(" danceability" , false , 0f , 1f )
339
+ object DANCEABILITY : TuneableTrackAttribute<Float>(" danceability" , false , 0f , 1f , Float : :class )
338
340
339
341
/* *
340
342
* The duration of the track in milliseconds.
341
343
*/
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 )
343
345
344
346
/* *
345
347
* Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity.
346
348
* Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy,
347
349
* while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute
348
350
* include dynamic range, perceived loudness, timbre, onset rate, and general entropy.
349
351
*/
350
- object ENERGY : TuneableTrackAttribute<Float>(" energy" , false , 0f , 1f )
352
+ object ENERGY : TuneableTrackAttribute<Float>(" energy" , false , 0f , 1f , Float : :class )
351
353
352
354
/* *
353
355
* Predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as
@@ -356,34 +358,34 @@ sealed class TuneableTrackAttribute<T : Number>(
356
358
* no vocal content. Values above 0.5 are intended to represent instrumental tracks, but
357
359
* confidence is higher as the value approaches 1.0.
358
360
*/
359
- object INSTRUMENTALNESS : TuneableTrackAttribute<Float>(" instrumentalness" , false , 0f , 1f )
361
+ object INSTRUMENTALNESS : TuneableTrackAttribute<Float>(" instrumentalness" , false , 0f , 1f , Float : :class )
360
362
361
363
/* *
362
364
* The key the track is in. Integers map to pitches using standard Pitch Class notation.
363
365
* E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on.
364
366
*/
365
- object KEY : TuneableTrackAttribute<Int>(" key" , true , 0 , 11 )
367
+ object KEY : TuneableTrackAttribute<Int>(" key" , true , 0 , 11 , Int : :class )
366
368
367
369
/* *
368
370
* Detects the presence of an audience in the recording. Higher liveness values represent an increased
369
371
* probability that the track was performed live. A value above 0.8 provides strong likelihood
370
372
* that the track is live.
371
373
*/
372
- object LIVENESS : TuneableTrackAttribute<Float>(" liveness" , false , 0f , 1f )
374
+ object LIVENESS : TuneableTrackAttribute<Float>(" liveness" , false , 0f , 1f , Float : :class )
373
375
374
376
/* *
375
377
* The overall loudness of a track in decibels (dB). Loudness values are averaged across the
376
378
* entire track and are useful for comparing relative loudness of tracks. Loudness is the
377
379
* quality of a sound that is the primary psychological correlate of physical strength (amplitude).
378
380
* Values typically range between -60 and 0 db.
379
381
*/
380
- object LOUDNESS : TuneableTrackAttribute<Float>(" loudness" , false , null , null )
382
+ object LOUDNESS : TuneableTrackAttribute<Float>(" loudness" , false , null , null , Float : :class )
381
383
382
384
/* *
383
385
* Mode indicates the modality (major or minor) of a track, the type of scale from which its
384
386
* melodic content is derived. Major is represented by 1 and minor is 0.
385
387
*/
386
- object MODE : TuneableTrackAttribute<Int>(" mode" , true , 0 , 1 )
388
+ object MODE : TuneableTrackAttribute<Int>(" mode" , true , 0 , 1 , Int : :class )
387
389
388
390
/* *
389
391
* 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>(
392
394
* the market parameter, it is expected to find relinked tracks with popularities that do not match
393
395
* 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.
394
396
*/
395
- object POPULARITY : TuneableTrackAttribute<Int>(" popularity" , true , 0 , 100 )
397
+ object POPULARITY : TuneableTrackAttribute<Int>(" popularity" , true , 0 , 100 , Int : :class )
396
398
397
399
/* *
398
400
* 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>(
402
404
* such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like
403
405
* tracks.
404
406
*/
405
- object SPEECHINESS : TuneableTrackAttribute<Float>(" speechiness" , false , 0f , 1f )
407
+ object SPEECHINESS : TuneableTrackAttribute<Float>(" speechiness" , false , 0f , 1f , Float : :class )
406
408
407
409
/* *
408
410
* The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the
409
411
* speed or pace of a given piece and derives directly from the average beat duration.
410
412
*/
411
- object TEMPO : TuneableTrackAttribute<Float>(" tempo" , false , 0f , null )
413
+ object TEMPO : TuneableTrackAttribute<Float>(" tempo" , false , 0f , null , Float : :class )
412
414
413
415
/* *
414
416
* An estimated overall time signature of a track. The time signature (meter)
415
417
* is a notational convention to specify how many beats are in each bar (or measure).
416
418
* The time signature ranges from 3 to 7 indicating time signatures of 3/4, to 7/4.
417
419
* A value of -1 may indicate no time signature, while a value of 1 indicates a rather complex or changing time signature.
418
420
*/
419
- object TIME_SIGNATURE : TuneableTrackAttribute<Int>(" time_signature" , true , -1 , 7 )
421
+ object TIME_SIGNATURE : TuneableTrackAttribute<Int>(" time_signature" , true , -1 , 7 , Int : :class )
420
422
421
423
/* *
422
424
* A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high
423
425
* valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence
424
426
* sound more negative (e.g. sad, depressed, angry).
425
427
*/
426
- object VALENCE : TuneableTrackAttribute<Float>(" valence" , false , 0f , 1f )
428
+ object VALENCE : TuneableTrackAttribute<Float>(" valence" , false , 0f , 1f , Float : :class )
427
429
428
430
override fun toString () = attribute
429
431
430
- fun asTrackAttribute (value : T ): TrackAttribute <T > {
432
+ fun < V : Number > asTrackAttribute (value : V ): TrackAttribute <T > {
431
433
require(! (min != null && min.toDouble() > value.toDouble())) { " Attribute value for $this must be greater than $min !" }
432
434
require(! (max != null && max.toDouble() < value.toDouble())) { " Attribute value for $this must be less than $max !" }
433
435
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
+ })
435
443
}
436
444
437
445
companion object {
438
446
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
453
461
)
454
462
}
455
463
}
456
464
457
465
data class TrackAttribute <T : Number >(val tuneableTrackAttribute : TuneableTrackAttribute <T >, val value : T ) {
458
466
companion object {
459
467
fun <T : Number > create (tuneableTrackAttribute : TuneableTrackAttribute <T >, value : T ) =
460
- tuneableTrackAttribute.asTrackAttribute(value)
468
+ tuneableTrackAttribute.asTrackAttribute(value)
461
469
}
462
470
}
0 commit comments