Compare commits

...

31 Commits

Author SHA1 Message Date
Max Kellermann
964804a4c2 release v0.21.15 2019-09-25 21:24:15 +02:00
Max Kellermann
92495d2b0b decoder/mpcdec: fix bogus ReplayGain values
Apparently, libmpcdec sets gain/peak variables to zero if they are not
present.  This clashes with our formula and results in bogus values
which cause noise during playback.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/640
2019-09-13 19:52:11 +02:00
Max Kellermann
9270829b5b ReplayGainInfo: move more code to a function 2019-09-13 19:50:49 +02:00
Max Kellermann
b6243a9945 decoder/mpcdec: merge duplicate code 2019-09-13 19:50:43 +02:00
Max Kellermann
496f88653d ReplayGainInfo: add static method Undefined() 2019-09-13 19:46:39 +02:00
Max Kellermann
5ef645df97 NEWS: add missing line for 818b7e0641 2019-09-08 12:54:16 +02:00
Max Kellermann
bf49c9e4e2 decoder/{dsf,dsdiff}: precalculate bit rate 2019-09-08 12:52:02 +02:00
Max Kellermann
0da9c91af2 decoder/{dsf,dsdiff}: fix displayed bit rate
The formula did not consider the channel count.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/639
2019-09-08 12:45:05 +02:00
Max Kellermann
193e637dd9 python/build/libs: update Boost to 1.71.0 2019-09-01 13:03:50 +02:00
Max Kellermann
928bee933d python/build/libs: update expat to 2.2.7 2019-09-01 13:02:56 +02:00
Max Kellermann
4d1720c886 python/build/libs: update CURL to 7.65.3 2019-09-01 13:02:04 +02:00
Max Kellermann
8f8ed87327 python/build/libs: update FFmpeg to 4.2 2019-09-01 13:00:26 +02:00
Max Kellermann
28a441c977 python/build/libs: update Opus to 1.3.1 2019-09-01 12:59:17 +02:00
Max Kellermann
8cf50b08f2 python/build/libs: update libogg to 1.3.4 2019-09-01 12:58:26 +02:00
Max Kellermann
818b7e0641 output/solaris: include sys/stropts.h only on Solaris
This header had been available for a long time on Linux, but was
removed in glibc 2.30.  This commit moves the `#include` line inside
the `#ifdef __sun` block and adds a fake declaration of `I_FLUSH` for
the Linux build.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/630
2019-08-22 11:41:12 +02:00
Max Kellermann
e70f40fac1 increment version number to 0.21.15 2019-08-22 11:40:17 +02:00
Max Kellermann
bc89ca92b4 release v0.21.14 2019-08-21 10:47:53 +02:00
Max Kellermann
b968e1b6de output/Thread: add missing return in exception handler 2019-08-21 10:20:17 +02:00
Max Kellermann
6c9f9c136b command/all: don't create new Response instance in exception handler
The new Response instance in the `catch` block didn't have the
`command` attribute set, so the error response didn't indicate which
command had failed, which however is required in the MPD protocol.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/628
2019-08-20 20:31:36 +02:00
Max Kellermann
9bff5f9e36 client/Process, command/all: add noexcept
Clarify that those can't throw, preparing for the next commit.
2019-08-20 20:28:15 +02:00
Max Kellermann
2bf26a2ff8 command/all: remove obsolete prototype 2019-08-20 20:28:10 +02:00
Max Kellermann
e33b50d9c5 command/all: simplify return from command_process() 2019-08-20 20:26:07 +02:00
Max Kellermann
21fa44c0d5 command/all: catch all exceptions 2019-08-20 20:23:54 +02:00
Max Kellermann
44444e1b89 decoder/Thread: on late SEEK, start decoder at seek position
Previously, a bogus value (whatever happened to be still in
`start_time`) was used.
2019-08-20 20:15:08 +02:00
Max Kellermann
ca450663d0 decoder/Control: work around crash after SEEK was too late
See code comment.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/629
2019-08-20 20:01:53 +02:00
Max Kellermann
f3d16f6d1b output/Thread: fix typo in comment 2019-08-13 13:08:40 +02:00
Max Kellermann
4464cdcc67 doc/protocol.rst: add missing newline to "albumart" example
This was missing in commit 0f488dcecf
2019-08-12 20:20:17 +02:00
Fredrik Noring
2d61e526de decoder/sidplay: Fix date field to have year but not company or author
Field 2 is called <released>, formerly used as <copyright>[1][2]. It is
formatted <year><space><company or author or group>, where <year> may be
<YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for example "1987", "199?", "19??"
or "1985-87". The <company or author or group> may be for example Rob
Hubbard. A full field may be for example "1987 Rob Hubbard".

This change splits the <released> field at the first <space>, to retain
the <year> part.

The 51823 SID files in High Voltage SID Collection (HVSC) version 71
have the following distribution of dates:

    333 19??         11 1990-92       6 1995-99       2 2006-08
    827 198?         88 1990-93    2140 1996        530 2007
     32 1982         69 1990-94       9 1996-97      15 2007-08
      1 1982-83      49 1990-95       2 1996-98       2 2007-09
    255 1983       3467 1991          5 1996-99       1 2007-10
    677 1984         75 1991-92    1840 1997        430 2008
    775 1985         65 1991-93       4 1997-98      23 2008-09
      3 1985-86      10 1991-94    1276 1998          1 2008-12
     10 1985-87      35 1991-97       4 1998-99     631 2009
    943 1986       3320 1992        865 1999          1 2009-10
     12 1986-87      26 1992-93      24 200?        645 2010
      5 1986-89      59 1992-94     590 2000          1 2010-12
   2083 1987          1 1992-96       4 2000-01     538 2011
     31 1987-88    2996 1993        727 2001          1 2011-12
     44 1987-89      42 1993-94     875 2002        651 2012
   2510 1988         12 1993-95       2 2002-04     811 2013
    129 1988-89       2 1993-97     844 2003        790 2014
     91 1988-90    2737 1994          3 2003-05     740 2015
     58 1988-91      16 1994-95     842 2004        792 2016
   3466 1989         20 1994-96       2 2004-05     775 2017
     95 1989-90      17 1994-97     707 2005        638 2018
    150 1989-91    2271 1995          1 2005-06     284 2019
   1077 199?          2 1995-96       2 2005-07
   2834 1990          4 1995-97     785 2006
    119 1990-91       2 1995-98       6 2006-07

References:

[1] https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt
[2] https://hvsc.c64.org/info
2019-08-10 10:50:51 +02:00
Fredrik Noring
7723c481db decoder/sidplay: Fix windows-1252 to utf-8 string conversion
High Voltage SID Collection (HVSC) metadata fields are encoded in
windows-1252, as described in DOCUMENTS/SID_file_format.txt:

https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt

If utf-8 transcoding fails, or the ICU library is unavailable, fall
back to plain ASCII and replace other characters with '?'.
2019-08-10 10:45:02 +02:00
Fredrik Noring
0ed10542cc decoder/sidplay: Fix song length initialisation during container scan
The song length was previously undetermined.
2019-08-09 15:39:36 +02:00
Max Kellermann
ab830f9afd increment version number to 0.21.14 2019-08-09 15:38:01 +02:00
19 changed files with 217 additions and 88 deletions

18
NEWS

@@ -1,3 +1,21 @@
ver 0.21.15 (2019/09/25)
* decoder
- dsdiff, dsf: fix displayed bit rate
- mpcdec: fix bogus ReplayGain values
* output
- solaris: fix build with glibc 2.30
ver 0.21.14 (2019/08/21)
* decoder
- sidplay: show track durations in database
- sidplay: convert tag values from Windows-1252 charset
- sidplay: strip text from "Date" tag
* player
- fix crash after song change
- fix seek position after restarting the decoder
* protocol
- include command name in error responses
ver 0.21.13 (2019/08/06)
* input
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="36"
android:versionName="0.21.13">
android:versionCode="38"
android:versionName="0.21.15">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.13'
version = '0.21.15'
# The full version, including alpha/beta/rc tags.
release = version

@@ -824,7 +824,8 @@ The music database
albumart
size: 1024768
binary: 8192
<8192 bytes>OK
<8192 bytes>
OK
:command:`count {FILTER} [group {GROUPTYPE}]`
Count the number of songs and their total playtime in

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.13',
version: '0.21.15',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',

@@ -15,8 +15,8 @@ libmpdclient = MesonProject(
)
libogg = AutotoolsProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
'lib/libogg.a',
[
'--disable-shared', '--enable-static',
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
)
opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
'lib/libopus.a',
[
'--disable-shared', '--enable-static',
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
'http://ffmpeg.org/releases/ffmpeg-4.2.tar.xz',
'023f10831a97ad93d798f53a3640e55cd564abfeba807ecbe8524dac4fedecd5',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
'http://curl.haxx.se/download/curl-7.65.3.tar.xz',
'f2d98854813948d157f6a91236ae34ca4a1b4cb302617cebad263d79b0235fea',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -365,8 +365,8 @@ curl = AutotoolsProject(
)
libexpat = AutotoolsProject(
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
'https://github.com/libexpat/libexpat/releases/download/R_2_2_7/expat-2.2.7.tar.bz2',
'cbc9102f4a31a8dafd42d642e9a3aa31e79a0aedaa1f6efd2795ebc83174ec18',
'lib/libexpat.a',
[
'--disable-shared', '--enable-static',
@@ -392,7 +392,7 @@ libnfs = AutotoolsProject(
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
'https://dl.bintray.com/boostorg/release/1.71.0/source/boost_1_71_0.tar.bz2',
'd73a8da01e8bf8c7eda40b4c84915071a8c8a0df4a6734537ddde4a8580524ee',
'include/boost/version.hpp',
)

@@ -38,6 +38,10 @@ struct ReplayGainTuple {
return gain > -100;
}
static constexpr ReplayGainTuple Undefined() noexcept {
return {-200.0f, 0.0f};
}
gcc_pure
float CalculateScale(const ReplayGainConfig &config) const noexcept;
};
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
return track.IsDefined() || album.IsDefined();
}
static constexpr ReplayGainInfo Undefined() noexcept {
return {
ReplayGainTuple::Undefined(),
ReplayGainTuple::Undefined(),
};
}
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
return mode == ReplayGainMode::ALBUM
? (album.IsDefined() ? album : track)

@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
extern size_t client_max_output_buffer_size;
CommandResult
client_process_line(Client &client, char *line);
client_process_line(Client &client, char *line) noexcept;
#endif

@@ -30,7 +30,7 @@
static CommandResult
client_process_command_list(Client &client, bool list_ok,
std::list<std::string> &&list)
std::list<std::string> &&list) noexcept
{
CommandResult ret = CommandResult::OK;
unsigned num = 0;
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
}
CommandResult
client_process_line(Client &client, char *line)
client_process_line(Client &client, char *line) noexcept
{
CommandResult ret;

@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
gcc_pure
static bool
command_available(gcc_unused const Partition &partition,
gcc_unused const struct command *cmd)
gcc_unused const struct command *cmd) noexcept
{
#ifdef ENABLE_SQLITE
if (StringIsEqual(cmd->cmd, "sticker"))
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
static CommandResult
PrintAvailableCommands(Response &r, const Partition &partition,
unsigned permission)
unsigned permission) noexcept
{
for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i];
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
}
static CommandResult
PrintUnavailableCommands(Response &r, unsigned permission)
PrintUnavailableCommands(Response &r, unsigned permission) noexcept
{
for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i];
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
}
void
command_init()
command_init() noexcept
{
#ifndef NDEBUG
/* ensure that the command list is sorted */
@@ -285,8 +286,9 @@ command_init()
#endif
}
gcc_pure
static const struct command *
command_lookup(const char *name)
command_lookup(const char *name) noexcept
{
unsigned a = 0, b = num_commands, i;
@@ -308,7 +310,7 @@ command_lookup(const char *name)
static bool
command_check_request(const struct command *cmd, Response &r,
unsigned permission, Request args)
unsigned permission, Request args) noexcept
{
if (cmd->permission != (permission & cmd->permission)) {
r.FormatError(ACK_ERROR_PERMISSION,
@@ -342,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
static const struct command *
command_checked_lookup(Response &r, unsigned permission,
const char *cmd_name, Request args)
const char *cmd_name, Request args) noexcept
{
const struct command *cmd = command_lookup(cmd_name);
if (cmd == nullptr) {
@@ -360,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
}
CommandResult
command_process(Client &client, unsigned num, char *line)
try {
command_process(Client &client, unsigned num, char *line) noexcept
{
Response r(client, num);
/* get the command name (first word on the line) */
@@ -389,34 +391,33 @@ try {
char *argv[COMMAND_ARGV_MAX];
Request args(argv, 0);
/* now parse the arguments (quoted or unquoted) */
try {
/* now parse the arguments (quoted or unquoted) */
while (true) {
if (args.size == COMMAND_ARGV_MAX) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
while (true) {
if (args.size == COMMAND_ARGV_MAX) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
}
char *a = tokenizer.NextParam();
if (a == nullptr)
break;
argv[args.size++] = a;
}
char *a = tokenizer.NextParam();
if (a == nullptr)
break;
/* look up and invoke the command handler */
argv[args.size++] = a;
const struct command *cmd =
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
if (cmd == nullptr)
return CommandResult::ERROR;
return cmd->handler(client, args, r);
} catch (...) {
PrintError(r, std::current_exception());
return CommandResult::ERROR;
}
/* look up and invoke the command handler */
const struct command *cmd =
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
CommandResult ret = cmd
? cmd->handler(client, args, r)
: CommandResult::ERROR;
return ret;
} catch (const std::exception &e) {
Response r(client, num);
PrintError(r, std::current_exception());
return CommandResult::ERROR;
}

@@ -25,12 +25,9 @@
class Client;
void
command_init();
void
command_finish();
command_init() noexcept;
CommandResult
command_process(Client &client, unsigned num, char *line);
command_process(Client &client, unsigned num, char *line) noexcept;
#endif

@@ -147,6 +147,18 @@ DecoderControl::Seek(SongTime t)
seek_error = false;
SynchronousCommandLocked(DecoderCommand::SEEK);
while (state == DecoderState::START)
/* If the decoder falls back to DecoderState::START,
this means that our SEEK command arrived too late,
and the decoder had meanwhile finished decoding and
went idle. Our SEEK command is finished, but that
means only that the decoder thread has launched the
decoder. To work around illegal states, we wait
until the decoder plugin has become ready. This is
a kludge, built on top of the "late seek" kludge.
Not exactly elegant, sorry. */
WaitForDecoder();
if (seek_error)
throw std::runtime_error("Decoder failed to seek");
}

@@ -455,6 +455,11 @@ static void
decoder_run_song(DecoderControl &dc,
const DetachedSong &song, const char *uri, Path path_fs)
{
if (dc.command == DecoderCommand::SEEK)
/* if the SEEK command arrived too late, start the
decoder at the seek position */
dc.start_time = dc.seek_time;
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
/* pass the song tag only if it's
authoritative, i.e. if it's a local

@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
unsigned channels, unsigned sample_rate,
const offset_type total_bytes)
{
const unsigned kbit_rate = channels * sample_rate / 1000;
const offset_type start_offset = is.GetOffset();
uint8_t buffer[8192];
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
bit_reverse_buffer(buffer, buffer + nbytes);
cmd = client.SubmitData(is, buffer, nbytes,
sample_rate / 1000);
kbit_rate);
}
return true;

@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
offset_type n_blocks,
bool bitreverse)
{
const unsigned kbit_rate = channels * sample_rate / 1000;
const size_t block_size = channels * DSF_BLOCK_SIZE;
const offset_type start_offset = is.GetOffset();
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
cmd = client.SubmitData(is,
interleaved_buffer, block_size,
sample_rate / 1000);
kbit_rate);
++i;
}

@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
*dest++ = mpc_to_mpd_sample(*src++);
}
static constexpr ReplayGainTuple
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
{
auto t = ReplayGainTuple::Undefined();
if (gain != 0 && peak != 0) {
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
t.peak = pow(10, peak / 256. / 20) / 32767;
}
return t;
}
static constexpr ReplayGainInfo
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
{
auto rgi = ReplayGainInfo::Undefined();
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
return rgi;
}
static void
mpcdec_decode(DecoderClient &client, InputStream &is)
{
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
mpcdec_sample_format,
info.channels);
ReplayGainInfo rgi;
rgi.Clear();
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767;
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
client.SubmitReplayGain(&rgi);
{
const auto rgi = ImportMpcdecReplayGain(info);
if (rgi.IsDefined())
client.SubmitReplayGain(&rgi);
}
client.Ready(audio_format, is.IsSeekable(),
SongTime::FromS(mpc_streaminfo_get_length(&info)));

@@ -25,6 +25,7 @@
#include "song/DetachedSong.hxx"
#include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx"
#include "lib/icu/Converter.hxx"
#ifdef HAVE_SIDPLAYFP
#include "fs/io/FileReader.hxx"
#include "util/RuntimeError.hxx"
@@ -32,6 +33,8 @@
#include "util/Macros.hxx"
#include "util/StringFormat.hxx"
#include "util/Domain.hxx"
#include "util/AllocatedString.hxx"
#include "util/CharUtil.hxx"
#include "system/ByteOrder.hxx"
#include "Log.hxx"
@@ -432,19 +435,70 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
} while (cmd != DecoderCommand::STOP);
}
static AllocatedString<char>
Windows1252ToUTF8(const char *s) noexcept
{
#ifdef HAVE_ICU_CONVERTER
try {
std::unique_ptr<IcuConverter>
converter(IcuConverter::Create("windows-1252"));
return converter->ToUTF8(s);
} catch (...) { }
#endif
/*
* Fallback to not transcoding windows-1252 to utf-8, that may result
* in invalid utf-8 unless nonprintable characters are replaced.
*/
auto t = AllocatedString<char>::Duplicate(s);
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
if (!IsPrintableASCII(t[i]))
t[i] = '?';
return t;
}
gcc_pure
static const char *
static AllocatedString<char>
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
{
#ifdef HAVE_SIDPLAYFP
return info.numberOfInfoStrings() > i
const char *s = info.numberOfInfoStrings() > i
? info.infoString(i)
: nullptr;
: "";
#else
return info.numberOfInfoStrings > i
const char *s = info.numberOfInfoStrings > i
? info.infoString[i]
: nullptr;
: "";
#endif
return Windows1252ToUTF8(s);
}
gcc_pure
static AllocatedString<char>
GetDateString(const SidTuneInfo &info) noexcept
{
/*
* Field 2 is called <released>, previously used as <copyright>.
* It is formatted <year><space><company or author or group>,
* where <year> may be <YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for
* example "1987", "199?", "19??" or "1985-87". The <company or
* author or group> may be for example Rob Hubbard. A full field
* may be for example "1987 Rob Hubbard".
*/
AllocatedString<char> release = GetInfoString(info, 2);
/* Keep the <year> part only for the date. */
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
if (std::isspace(release[i])) {
release[i] = AllocatedString<char>::SENTINEL;
break;
}
return release;
}
static void
@@ -452,27 +506,25 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
TagHandler &handler) noexcept
{
/* title */
const char *title = GetInfoString(info, 0);
if (title == nullptr)
title = "";
const auto title = GetInfoString(info, 0);
if (n_tracks > 1) {
const auto tag_title =
StringFormat<1024>("%s (%u/%u)",
title, track, n_tracks);
handler.OnTag(TAG_TITLE, tag_title);
title.c_str(), track, n_tracks);
handler.OnTag(TAG_TITLE, tag_title.c_str());
} else
handler.OnTag(TAG_TITLE, title);
handler.OnTag(TAG_TITLE, title.c_str());
/* artist */
const char *artist = GetInfoString(info, 1);
if (artist != nullptr)
handler.OnTag(TAG_ARTIST, artist);
const auto artist = GetInfoString(info, 1);
if (!artist.empty())
handler.OnTag(TAG_ARTIST, artist.c_str());
/* date */
const char *date = GetInfoString(info, 2);
if (date != nullptr)
handler.OnTag(TAG_DATE, date);
const auto date = GetDateString(info);
if (!date.empty())
handler.OnTag(TAG_DATE, date.c_str());
/* track */
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
@@ -547,6 +599,10 @@ sidplay_container_scan(Path path_fs)
AddTagHandler h(tag_builder);
ScanSidTuneInfo(info, i, n_tracks, h);
const SignedSongTime duration = get_song_length(tune);
if (!duration.IsNegative())
h.OnDuration(SongTime(duration));
char track_name[32];
/* Construct container/tune path names, eg.
Delta.sid/tune_001.sid */

@@ -159,6 +159,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
} catch (...) {
LogError(std::current_exception());
Failure(std::current_exception());
return;
}
if (f != in_audio_format || f != output->out_audio_format)
@@ -458,7 +459,7 @@ AudioOutputControl::Task() noexcept
case Command::RELEASE:
if (!open) {
/* the output has failed after
the PAUSE command was submitted; bail
the RELEASE command was submitted; bail
out */
CommandFinished();
break;

@@ -22,7 +22,6 @@
#include "system/FileDescriptor.hxx"
#include "system/Error.hxx"
#include <sys/stropts.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -31,11 +30,18 @@
#ifdef __sun
#include <sys/audio.h>
#include <sys/stropts.h>
#else
/* some fake declarations that allow build this plugin on systems
other than Solaris, just to see if it compiles */
#include <sys/ioctl.h>
#ifndef I_FLUSH
#define I_FLUSH 0
#endif
#define AUDIO_GETINFO 0
#define AUDIO_SETINFO 0
#define AUDIO_ENCODING_LINEAR 0