diff options
author | Max Kellermann <max@duempel.org> | 2013-07-28 12:20:50 +0200 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2013-07-28 12:20:50 +0200 |
commit | 352d7f477eaf8a6a8536feb059a32a8b61ccb9ca (patch) | |
tree | d9deb8a4accbb5c087d1d59e473f3e5a9fce268c /src | |
parent | 1fcf35ad3b10c241b8b5266c46a5fe99ce2c57d9 (diff) | |
download | mpd-352d7f477eaf8a6a8536feb059a32a8b61ccb9ca.tar.gz mpd-352d7f477eaf8a6a8536feb059a32a8b61ccb9ca.tar.xz mpd-352d7f477eaf8a6a8536feb059a32a8b61ccb9ca.zip |
decoder/{dsf,dsdiff}: convert to C++
Diffstat (limited to 'src')
-rw-r--r-- | src/DecoderList.cxx | 4 | ||||
-rw-r--r-- | src/decoder/DsdLib.cxx (renamed from src/decoder/dsdlib.c) | 25 | ||||
-rw-r--r-- | src/decoder/DsdLib.hxx (renamed from src/decoder/dsdlib.h) | 10 | ||||
-rw-r--r-- | src/decoder/DsdiffDecoderPlugin.cxx (renamed from src/decoder/dsdiff_decoder_plugin.c) | 130 | ||||
-rw-r--r-- | src/decoder/DsdiffDecoderPlugin.hxx (renamed from src/decoder/dsdiff_decoder_plugin.h) | 2 | ||||
-rw-r--r-- | src/decoder/DsfDecoderPlugin.cxx (renamed from src/decoder/dsf_decoder_plugin.c) | 50 | ||||
-rw-r--r-- | src/decoder/DsfDecoderPlugin.hxx (renamed from src/decoder/dsf_decoder_plugin.h) | 2 |
7 files changed, 109 insertions, 114 deletions
diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx index 3cde7db72..d8035a737 100644 --- a/src/DecoderList.cxx +++ b/src/DecoderList.cxx @@ -23,8 +23,8 @@ #include "conf.h" #include "mpd_error.h" #include "decoder/pcm_decoder_plugin.h" -#include "decoder/dsdiff_decoder_plugin.h" -#include "decoder/dsf_decoder_plugin.h" +#include "decoder/DsdiffDecoderPlugin.hxx" +#include "decoder/DsfDecoderPlugin.hxx" #include "decoder/FlacDecoderPlugin.h" #include "decoder/OpusDecoderPlugin.h" #include "decoder/VorbisDecoderPlugin.h" diff --git a/src/decoder/dsdlib.c b/src/decoder/DsdLib.cxx index d3043fb05..dd7ea70c9 100644 --- a/src/decoder/dsdlib.c +++ b/src/decoder/DsdLib.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,13 +24,14 @@ */ #include "config.h" -#include "dsf_decoder_plugin.h" +#include "DsdLib.hxx" #include "decoder_api.h" #include "util/bit_reverse.h" #include "tag_handler.h" + +extern "C" { #include "tag_id3.h" -#include "dsdlib.h" -#include "dsdiff_decoder_plugin.h" +} #include <unistd.h> #include <stdio.h> /* for SEEK_SET, SEEK_CUR */ @@ -42,8 +43,8 @@ bool dsdlib_id_equals(const struct dsdlib_id *id, const char *s) { - assert(id != NULL); - assert(s != NULL); + assert(id != nullptr); + assert(s != nullptr); assert(strlen(s) == sizeof(id->value)); return memcmp(id->value, s, sizeof(id->value)) == 0; @@ -65,7 +66,7 @@ dsdlib_skip_to(struct decoder *decoder, struct input_stream *is, goffset offset) { if (input_stream_is_seekable(is)) - return input_stream_seek(is, offset, SEEK_SET, NULL); + return input_stream_seek(is, offset, SEEK_SET, nullptr); if (input_stream_get_offset(is) > offset) return false; @@ -98,7 +99,7 @@ dsdlib_skip(struct decoder *decoder, struct input_stream *is, return true; if (input_stream_is_seekable(is)) - return input_stream_seek(is, delta, SEEK_CUR, NULL); + return input_stream_seek(is, delta, SEEK_CUR, nullptr); char buffer[8192]; while (delta > 0) { @@ -132,10 +133,10 @@ dsdlib_tag_id3(struct input_stream *is, if (tagoffset == 0) return; - if (!dsdlib_skip_to(NULL, is, tagoffset)) + if (!dsdlib_skip_to(nullptr, is, tagoffset)) return; - struct id3_tag *id3_tag = NULL; + struct id3_tag *id3_tag = nullptr; id3_length_t count; /* Prevent broken files causing problems */ @@ -154,11 +155,11 @@ dsdlib_tag_id3(struct input_stream *is, id3_byte_t *dsdid3data; dsdid3data = dsdid3; - if (!dsdlib_read(NULL, is, dsdid3data, count)) + if (!dsdlib_read(nullptr, is, dsdid3data, count)) return; id3_tag = id3_tag_parse(dsdid3data, count); - if (id3_tag == NULL) + if (id3_tag == nullptr) return; scan_id3_tag(id3_tag, handler, handler_ctx); diff --git a/src/decoder/dsdlib.h b/src/decoder/DsdLib.hxx index 0912740c3..2a8e15190 100644 --- a/src/decoder/dsdlib.h +++ b/src/decoder/DsdLib.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_DSDLIB_H -#define MPD_DECODER_DSDLIB_H +#ifndef MPD_DECODER_DSDLIB_HXX +#define MPD_DECODER_DSDLIB_HXX + +#include <stdlib.h> + +#include <glib.h> struct dsdlib_id { char value[4]; diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/DsdiffDecoderPlugin.cxx index 44d12d899..ed2a533b0 100644 --- a/src/decoder/dsdiff_decoder_plugin.c +++ b/src/decoder/DsdiffDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,12 +27,12 @@ */ #include "config.h" -#include "dsdiff_decoder_plugin.h" +#include "DsdiffDecoderPlugin.hxx" #include "decoder_api.h" #include "audio_check.h" #include "util/bit_reverse.h" #include "tag_handler.h" -#include "dsdlib.h" +#include "DsdLib.hxx" #include "tag_handler.h" #include <unistd.h> @@ -41,15 +41,25 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "dsdiff" -struct dsdiff_header { +struct DsdiffHeader { struct dsdlib_id id; uint32_t size_high, size_low; struct dsdlib_id format; }; -struct dsdiff_chunk_header { +struct DsdiffChunkHeader { struct dsdlib_id id; uint32_t size_high, size_low; + + /** + * Read the "size" attribute from the specified header, converting it + * to the host byte order if needed. + */ + gcc_const + uint64_t GetSize() const { + return (((uint64_t)GUINT32_FROM_BE(size_high)) << 32) | + ((uint64_t)GUINT32_FROM_BE(size_low)); + } }; /** struct for DSDIFF native Artist and Title tags */ @@ -57,7 +67,7 @@ struct dsdiff_native_tag { uint32_t size; }; -struct dsdiff_metadata { +struct DsdiffMetaData { unsigned sample_rate, channels; bool bitreverse; uint64_t chunk_size; @@ -80,18 +90,6 @@ dsdiff_init(const struct config_param *param) return true; } -/** - * Read the "size" attribute from the specified header, converting it - * to the host byte order if needed. - */ -G_GNUC_CONST -static uint64_t -dsdiff_chunk_size(const struct dsdiff_chunk_header *header) -{ - return (((uint64_t)GUINT32_FROM_BE(header->size_high)) << 32) | - ((uint64_t)GUINT32_FROM_BE(header->size_low)); -} - static bool dsdiff_read_id(struct decoder *decoder, struct input_stream *is, struct dsdlib_id *id) @@ -101,17 +99,17 @@ dsdiff_read_id(struct decoder *decoder, struct input_stream *is, static bool dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is, - struct dsdiff_chunk_header *header) + DsdiffChunkHeader *header) { return dsdlib_read(decoder, is, header, sizeof(*header)); } static bool dsdiff_read_payload(struct decoder *decoder, struct input_stream *is, - const struct dsdiff_chunk_header *header, + const DsdiffChunkHeader *header, void *data, size_t length) { - uint64_t size = dsdiff_chunk_size(header); + uint64_t size = header->GetSize(); if (size != (uint64_t)length) return false; @@ -124,16 +122,16 @@ dsdiff_read_payload(struct decoder *decoder, struct input_stream *is, */ static bool dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, + DsdiffMetaData *metadata, goffset end_offset) { - struct dsdiff_chunk_header header; + DsdiffChunkHeader header; while ((goffset)(input_stream_get_offset(is) + sizeof(header)) <= end_offset) { if (!dsdiff_read_chunk_header(decoder, is, &header)) return false; goffset chunk_end_offset = input_stream_get_offset(is) - + dsdiff_chunk_size(&header); + + header.GetSize(); if (chunk_end_offset > end_offset) return false; @@ -147,7 +145,7 @@ dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, metadata->sample_rate = GUINT32_FROM_BE(sample_rate); } else if (dsdlib_id_equals(&header.id, "CHNL")) { uint16_t channels; - if (dsdiff_chunk_size(&header) < sizeof(channels) || + if (header.GetSize() < sizeof(channels) || !dsdlib_read(decoder, is, &channels, sizeof(channels)) || !dsdlib_skip_to(decoder, is, chunk_end_offset)) @@ -156,7 +154,7 @@ dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, metadata->channels = GUINT16_FROM_BE(channels); } else if (dsdlib_id_equals(&header.id, "CMPR")) { struct dsdlib_id type; - if (dsdiff_chunk_size(&header) < sizeof(type) || + if (header.GetSize() < sizeof(type) || !dsdlib_read(decoder, is, &type, sizeof(type)) || !dsdlib_skip_to(decoder, is, chunk_end_offset)) @@ -182,10 +180,10 @@ dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, */ static bool dsdiff_read_prop(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, - const struct dsdiff_chunk_header *prop_header) + DsdiffMetaData *metadata, + const DsdiffChunkHeader *prop_header) { - uint64_t prop_size = dsdiff_chunk_size(prop_header); + uint64_t prop_size = prop_header->GetSize(); goffset end_offset = input_stream_get_offset(is) + prop_size; struct dsdlib_id prop_id; @@ -206,12 +204,12 @@ dsdiff_handle_native_tag(struct input_stream *is, void *handler_ctx, goffset tagoffset, enum tag_type type) { - if (!dsdlib_skip_to(NULL, is, tagoffset)) + if (!dsdlib_skip_to(nullptr, is, tagoffset)) return; struct dsdiff_native_tag metatag; - if (!dsdlib_read(NULL, is, &metatag, sizeof(metatag))) + if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag))) return; uint32_t length = GUINT32_FROM_BE(metatag.size); @@ -224,7 +222,7 @@ dsdiff_handle_native_tag(struct input_stream *is, char *label; label = string; - if (!dsdlib_read(NULL, is, label, (size_t)length)) + if (!dsdlib_read(nullptr, is, label, (size_t)length)) return; string[length] = '\0'; @@ -242,8 +240,8 @@ dsdiff_handle_native_tag(struct input_stream *is, static bool dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, - struct dsdiff_chunk_header *chunk_header, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header, const struct tag_handler *handler, void *handler_ctx) { @@ -263,7 +261,7 @@ dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is, const goffset size = input_stream_get_size(is); while (input_stream_get_offset(is) < size) { - uint64_t chunk_size = dsdiff_chunk_size(chunk_header); + uint64_t chunk_size = chunk_header->GetSize(); /* DIIN chunk, is directly followed by other chunks */ if (dsdlib_id_equals(&chunk_header->id, "DIIN")) @@ -271,19 +269,19 @@ dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is, /* DIAR chunk - DSDIFF native tag for Artist */ if (dsdlib_id_equals(&chunk_header->id, "DIAR")) { - chunk_size = dsdiff_chunk_size(chunk_header); + chunk_size = chunk_header->GetSize(); metadata->diar_offset = input_stream_get_offset(is); } /* DITI chunk - DSDIFF native tag for Title */ if (dsdlib_id_equals(&chunk_header->id, "DITI")) { - chunk_size = dsdiff_chunk_size(chunk_header); + chunk_size = chunk_header->GetSize(); metadata->diti_offset = input_stream_get_offset(is); } #ifdef HAVE_ID3TAG /* 'ID3 ' chunk, offspec. Used by sacdextract */ if (dsdlib_id_equals(&chunk_header->id, "ID3 ")) { - chunk_size = dsdiff_chunk_size(chunk_header); + chunk_size = chunk_header->GetSize(); metadata->id3_offset = input_stream_get_offset(is); metadata->id3_size = chunk_size; } @@ -328,10 +326,10 @@ dsdiff_read_metadata_extra(struct decoder *decoder, struct input_stream *is, */ static bool dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is, - struct dsdiff_metadata *metadata, - struct dsdiff_chunk_header *chunk_header) + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header) { - struct dsdiff_header header; + DsdiffHeader header; if (!dsdlib_read(decoder, is, &header, sizeof(header)) || !dsdlib_id_equals(&header.id, "FRM8") || !dsdlib_id_equals(&header.format, "DSD ")) @@ -347,14 +345,12 @@ dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is, chunk_header)) return false; } else if (dsdlib_id_equals(&chunk_header->id, "DSD ")) { - uint64_t chunk_size; - chunk_size = dsdiff_chunk_size(chunk_header); + const uint64_t chunk_size = chunk_header->GetSize(); metadata->chunk_size = chunk_size; return true; } else { /* ignore unknown chunk */ - uint64_t chunk_size; - chunk_size = dsdiff_chunk_size(chunk_header); + const uint64_t chunk_size = chunk_header->GetSize(); goffset chunk_end_offset = input_stream_get_offset(is) + chunk_size; @@ -429,17 +425,14 @@ dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is, static void dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is) { - struct dsdiff_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; + DsdiffMetaData metadata; - struct dsdiff_chunk_header chunk_header; + DsdiffChunkHeader chunk_header; /* check if it is is a proper DFF file */ if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header)) return; - GError *error = NULL; + GError *error = nullptr; struct audio_format audio_format; if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, SAMPLE_FORMAT_DSD, @@ -461,7 +454,7 @@ dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is) chunk from a DFF file */ while (true) { - chunk_size = dsdiff_chunk_size(&chunk_header); + chunk_size = chunk_header.GetSize(); if (dsdlib_id_equals(&chunk_header.id, "DSD ")) { if (!dsdiff_decode_chunk(decoder, is, @@ -487,20 +480,17 @@ dsdiff_scan_stream(struct input_stream *is, G_GNUC_UNUSED const struct tag_handler *handler, G_GNUC_UNUSED void *handler_ctx) { - struct dsdiff_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; + DsdiffMetaData metadata; + DsdiffChunkHeader chunk_header; - struct dsdiff_chunk_header chunk_header; /* First check for DFF metadata */ - if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header)) + if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header)) return false; struct audio_format audio_format; if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, SAMPLE_FORMAT_DSD, - metadata.channels, NULL)) + metadata.channels, nullptr)) /* refuse to parse files which we cannot play anyway */ return false; @@ -510,7 +500,7 @@ dsdiff_scan_stream(struct input_stream *is, tag_handler_invoke_duration(handler, handler_ctx, songtime); /* Read additional metadata and created tags if available */ - dsdiff_read_metadata_extra(NULL, is, &metadata, &chunk_header, + dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, handler, handler_ctx); return true; @@ -518,19 +508,23 @@ dsdiff_scan_stream(struct input_stream *is, static const char *const dsdiff_suffixes[] = { "dff", - NULL + nullptr }; static const char *const dsdiff_mime_types[] = { "application/x-dff", - NULL + nullptr }; const struct decoder_plugin dsdiff_decoder_plugin = { - .name = "dsdiff", - .init = dsdiff_init, - .stream_decode = dsdiff_stream_decode, - .scan_stream = dsdiff_scan_stream, - .suffixes = dsdiff_suffixes, - .mime_types = dsdiff_mime_types, + "dsdiff", + dsdiff_init, + nullptr, + dsdiff_stream_decode, + nullptr, + nullptr, + dsdiff_scan_stream, + nullptr, + dsdiff_suffixes, + dsdiff_mime_types, }; diff --git a/src/decoder/dsdiff_decoder_plugin.h b/src/decoder/DsdiffDecoderPlugin.hxx index 452f9050b..c50605457 100644 --- a/src/decoder/dsdiff_decoder_plugin.h +++ b/src/decoder/DsdiffDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder/dsf_decoder_plugin.c b/src/decoder/DsfDecoderPlugin.cxx index 23576a629..a6f575d97 100644 --- a/src/decoder/dsf_decoder_plugin.c +++ b/src/decoder/DsfDecoderPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,11 +28,11 @@ */ #include "config.h" -#include "dsf_decoder_plugin.h" +#include "DsfDecoderPlugin.hxx" #include "decoder_api.h" #include "audio_check.h" #include "util/bit_reverse.h" -#include "dsdlib.h" +#include "DsdLib.hxx" #include "tag_handler.h" #include <unistd.h> @@ -41,7 +41,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "dsf" -struct dsf_metadata { +struct DsfMetaData { unsigned sample_rate, channels; bool bitreverse; uint64_t chunk_size; @@ -51,7 +51,7 @@ struct dsf_metadata { #endif }; -struct dsf_header { +struct DsfHeader { /** DSF header id: "DSD " */ struct dsdlib_id id; /** DSD chunk size, including id = 28 */ @@ -63,8 +63,7 @@ struct dsf_header { }; /** DSF file fmt chunk */ -struct dsf_fmt_chunk { - +struct DsfFmtChunk { /** id: "fmt " */ struct dsdlib_id id; /** fmt chunk size, including id, normally 52 */ @@ -89,7 +88,7 @@ struct dsf_fmt_chunk { uint32_t reserved; }; -struct dsf_data_chunk { +struct DsfDataChunk { struct dsdlib_id id; /** "data" chunk size, includes header (id+size) */ uint32_t size_low, size_high; @@ -100,10 +99,10 @@ struct dsf_data_chunk { */ static bool dsf_read_metadata(struct decoder *decoder, struct input_stream *is, - struct dsf_metadata *metadata) + DsfMetaData *metadata) { uint64_t chunk_size; - struct dsf_header dsf_header; + DsfHeader dsf_header; if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) || !dsdlib_id_equals(&dsf_header.id, "DSD ")) return false; @@ -121,7 +120,7 @@ dsf_read_metadata(struct decoder *decoder, struct input_stream *is, #endif /* read the 'fmt ' chunk of the DSF file */ - struct dsf_fmt_chunk dsf_fmt_chunk; + DsfFmtChunk dsf_fmt_chunk; if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || !dsdlib_id_equals(&dsf_fmt_chunk.id, "fmt ")) return false; @@ -150,7 +149,7 @@ dsf_read_metadata(struct decoder *decoder, struct input_stream *is, return false; /* read the 'data' chunk of the DSF file */ - struct dsf_data_chunk data_chunk; + DsfDataChunk data_chunk; if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) || !dsdlib_id_equals(&data_chunk.id, "data")) return false; @@ -280,12 +279,8 @@ dsf_decode_chunk(struct decoder *decoder, struct input_stream *is, static void dsf_stream_decode(struct decoder *decoder, struct input_stream *is) { - struct dsf_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; - /* check if it is a proper DSF file */ + DsfMetaData metadata; if (!dsf_read_metadata(decoder, is, &metadata)) return; @@ -317,12 +312,8 @@ dsf_scan_stream(struct input_stream *is, G_GNUC_UNUSED const struct tag_handler *handler, G_GNUC_UNUSED void *handler_ctx) { - struct dsf_metadata metadata = { - .sample_rate = 0, - .channels = 0, - }; - /* check DSF metadata */ + DsfMetaData metadata; if (!dsf_read_metadata(NULL, is, &metadata)) return false; @@ -356,9 +347,14 @@ static const char *const dsf_mime_types[] = { }; const struct decoder_plugin dsf_decoder_plugin = { - .name = "dsf", - .stream_decode = dsf_stream_decode, - .scan_stream = dsf_scan_stream, - .suffixes = dsf_suffixes, - .mime_types = dsf_mime_types, + "dsf", + nullptr, + nullptr, + dsf_stream_decode, + nullptr, + nullptr, + dsf_scan_stream, + nullptr, + dsf_suffixes, + dsf_mime_types, }; diff --git a/src/decoder/dsf_decoder_plugin.h b/src/decoder/DsfDecoderPlugin.hxx index 401d3fed7..749032d1f 100644 --- a/src/decoder/dsf_decoder_plugin.h +++ b/src/decoder/DsfDecoderPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify |