4970 lines
115 KiB
C++
4970 lines
115 KiB
C++
/*
|
|
----
|
|
This file is part of SECONDO.
|
|
|
|
Copyright (C) 2004, University in Hagen, Department of Computer Science,
|
|
Database Systems for New Applications.
|
|
|
|
SECONDO is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
SECONDO is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with SECONDO; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
----
|
|
|
|
//paragraph [1] Title: [{\Large \bf \begin{center}] [\end{center}}]
|
|
//paragraph [10] Footnote: [{\footnote{] [}}]
|
|
//[TOC] [\tableofcontents]
|
|
|
|
[1] MP3 Algebra
|
|
|
|
December 2003 R. Bozic, D. Riewenherm, M. Guenster
|
|
|
|
December 2005, Victor Almeida deleted the deprecated algebra levels
|
|
(~executable~, ~descriptive~, and ~hibrid~). Only the executable
|
|
level remains. Models are also removed from type constructors.
|
|
|
|
This algebra provides operators which deals with MP3, ID3 and Lyrics data.
|
|
Although an MP3 file contains ID3 and Lyrics this algebra provides three
|
|
type constructors to be able to handle these informations seperated from
|
|
mp3s.
|
|
|
|
i) mp3
|
|
|
|
ii) id3
|
|
|
|
iii) lyrics
|
|
|
|
Furthermore it provides the following operators:
|
|
|
|
i) ~savemp3to~ - saves an MP3 object into an MP3 file.
|
|
|
|
ii) ~version~ - returns the MP3 version.
|
|
|
|
iii) ~frequency~ - returns the used frequency of an MP3.
|
|
|
|
iv) ~framecount~ - returns the number of frames in an MP3.
|
|
|
|
v) ~length~ - returns the length of a song in seconds.
|
|
|
|
vi) ~bitrate~ - returns the bitrate of an MP3.
|
|
|
|
vii) ~concatmp3~ - concats two MP3 objects.
|
|
|
|
viii) ~submp3~ - returns a subset of an M3 object.
|
|
|
|
ix) ~putid3~ - put a new ID3 tag into an MP3.
|
|
|
|
x) ~removeid3~ - removes an ID3 tag from an MP3.
|
|
|
|
xi) ~getid3~ - get the ID3 tag from an MP3.
|
|
|
|
xii) ~author~ - returns the author of an ID3.
|
|
|
|
xiii) ~titleof~ - returns the title of an ID3.
|
|
|
|
xiv) ~album~ - returns the album of an ID3.
|
|
|
|
xv) ~comment~ - returns the comment of an ID3.
|
|
|
|
xvi) ~track~ - returns the track number of an ID3 (V1.1)
|
|
|
|
xvii) ~songyear~ - returns the year of an ID3.
|
|
|
|
xviii) ~genre~ - returns the genre of an ID3.
|
|
|
|
xix) ~words~ - returns the lyrics line at a given point a time.
|
|
|
|
xx) ~getlyrics~ - returns the lyrics from an MP3.
|
|
|
|
xxi) ~putlyrics~ - put lyrics into an MP3.
|
|
|
|
xxii) ~removelyrics~ - remove a lyrics into an MP3.
|
|
|
|
[TOC]
|
|
|
|
1 Defines and includes
|
|
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fstream>
|
|
|
|
#include "Algebra.h"
|
|
#include "NestedList.h"
|
|
#include "QueryProcessor.h"
|
|
#include "StandardTypes.h"
|
|
#include "Algebras/FText/FTextAlgebra.h"
|
|
#include "Attribute.h"
|
|
#include "FileSystem.h"
|
|
#include "Tools/Flob/Flob.h"
|
|
#include "Tools/Flob/DbArray.h"
|
|
#include "Base64.h"
|
|
#include "Symbols.h"
|
|
#include "ListUtils.h"
|
|
|
|
|
|
#include "GenericTC.h"
|
|
#include "Stream.h"
|
|
#include "NList.h"
|
|
#include "FVector.h"
|
|
|
|
|
|
#include <essentia/algorithmfactory.h>
|
|
#include <essentia/essentiamath.h>
|
|
#include <essentia/pool.h>
|
|
using namespace essentia;
|
|
using namespace essentia::standard;
|
|
|
|
|
|
extern NestedList* nl;
|
|
extern QueryProcessor *qp;
|
|
|
|
using namespace std;
|
|
|
|
namespace mp3{
|
|
|
|
/*
|
|
Type Constructor ~fvector~
|
|
|
|
A.1 class ~fvector~
|
|
|
|
*/
|
|
|
|
// create a TypeConstructor instance
|
|
GenTC<FVector> FVectorTC;
|
|
|
|
|
|
|
|
// A.2 Operator ~getfvectors~
|
|
// calculates feature vectors from an MP3-file
|
|
|
|
|
|
|
|
|
|
// A.2.1 Type mapping function of operator ~getfvectors~
|
|
|
|
// Operator ~getfvector~ accepts a filename of a MP3 and a int
|
|
// to select which feature should be extracted and returns a
|
|
// stream of fvectors
|
|
// ---- (filename text) x (select int) -> stream(fvector)
|
|
|
|
|
|
ListExpr getfvectorsTM(ListExpr args){
|
|
// check for correct number of arguments
|
|
if (! nl-> HasLength (args ,2)){
|
|
return listutils :: typeError ( " wrong number of arguments ");
|
|
}
|
|
|
|
// first argument must be a string
|
|
// second argument must be a single integer
|
|
if (!FText::checkType ( nl-> First( args ))
|
|
|| !CcInt::checkType ( nl-> Second( args )) ){
|
|
return listutils :: typeError ( " text x int expected " );
|
|
}
|
|
// create the result type stream (fvector)
|
|
return nl->TwoElemList( nl->SymbolAtom(Stream<FVector>::BasicType()),
|
|
nl->SymbolAtom(FVector::BasicType()) );
|
|
|
|
}
|
|
|
|
// A.2.2 Value mapping function of operator ~getfvectors~
|
|
|
|
|
|
// LocalInfo class to save the state of the operator.
|
|
// The class also encapsulates the calculations of the
|
|
// DSP-library essentia.
|
|
|
|
|
|
class getFVectorsLI{
|
|
public:
|
|
// constructor: initializes the class from the string argument
|
|
// and do the computation with the extern library
|
|
// resuls are stored in the private members
|
|
getFVectorsLI (FText* arg) : filename(""), index(0){
|
|
if(arg->IsDefined()){
|
|
filename = arg->GetValue();
|
|
|
|
// Essentia Part -> TODO select auswerten!!!
|
|
|
|
string audioFilename = filename;
|
|
essentia::init();
|
|
|
|
Pool pool;
|
|
|
|
/////// PARAMS //////////////
|
|
int sampleRate = 44100;
|
|
int frameSize = 2048;
|
|
int hopSize = 1024;
|
|
|
|
// audioloader -> framecutter -> windowing -> FFT -> MFCC
|
|
AlgorithmFactory& factory = standard::AlgorithmFactory::instance();
|
|
|
|
Algorithm* audio = factory.create("MonoLoader",
|
|
"filename", audioFilename,
|
|
"sampleRate", sampleRate);
|
|
|
|
Algorithm* fc = factory.create("FrameCutter",
|
|
"frameSize", frameSize,
|
|
"hopSize", hopSize);
|
|
|
|
Algorithm* w = factory.create("Windowing",
|
|
"type", "blackmanharris62");
|
|
|
|
Algorithm* spec = factory.create("Spectrum");
|
|
|
|
Algorithm* mfcc = factory.create("MFCC",
|
|
"highFrequencyBound",20000);
|
|
|
|
/////////// CONNECTING THE ALGORITHMS ////////////////
|
|
|
|
// Audio -> FrameCutter
|
|
vector<Real> audioBuffer;
|
|
|
|
audio->output("audio").set(audioBuffer);
|
|
fc->input("signal").set(audioBuffer);
|
|
|
|
// FrameCutter -> Windowing -> Spectrum
|
|
vector<Real> frame, windowedFrame;
|
|
|
|
fc->output("frame").set(frame);
|
|
w->input("frame").set(frame);
|
|
|
|
w->output("frame").set(windowedFrame);
|
|
spec->input("frame").set(windowedFrame);
|
|
|
|
// Spectrum -> MFCC
|
|
vector<Real> spectrum, mfccCoeffs, mfccBands;
|
|
|
|
spec->output("spectrum").set(spectrum);
|
|
mfcc->input("spectrum").set(spectrum);
|
|
|
|
mfcc->output("bands").set(mfccBands);
|
|
mfcc->output("mfcc").set(mfccCoeffs);
|
|
|
|
/////////// STARTING THE ALGORITHMS //////////////////
|
|
audio->compute();
|
|
|
|
while (true) {
|
|
|
|
// compute a frame
|
|
fc->compute();
|
|
// if it was the last one (ie: it was empty), then we're done.
|
|
if (!frame.size()) {
|
|
break;
|
|
}
|
|
|
|
// if the frame is silent, just drop it and go on processing
|
|
if (isSilent(frame)) continue;
|
|
|
|
w->compute();
|
|
spec->compute();
|
|
mfcc->compute();
|
|
|
|
pool.add("lowlevel.mfcc", mfccCoeffs);
|
|
} //while
|
|
|
|
|
|
// Ergebnisse in Map speichern und Essentia beenden.
|
|
m=pool.getVectorRealPool();
|
|
it=m.begin();
|
|
|
|
|
|
|
|
delete audio;
|
|
delete fc;
|
|
delete w;
|
|
delete spec;
|
|
delete mfcc;
|
|
|
|
essentia::shutdown();
|
|
//end Essentia part
|
|
|
|
} //if
|
|
}
|
|
// destructor
|
|
~getFVectorsLI(){}
|
|
|
|
// this function returns the next result or null if the input is
|
|
// exhausted
|
|
FVector* getNext(){
|
|
if(index>= it->second.size()) {
|
|
return 0;
|
|
}
|
|
// read next fvector
|
|
vector<Real> fv = it->second[index];
|
|
//read dimension of fvector
|
|
int dim = it->second[index].size();
|
|
|
|
//create Secondo FVector and copy result
|
|
FVector* res = new FVector(1.0);
|
|
for (int i=0;i<dim; i++){
|
|
res->append(fv[i]);
|
|
}
|
|
|
|
// increment index
|
|
index ++;
|
|
// return result fvector
|
|
return res;
|
|
}
|
|
|
|
private:
|
|
string filename; //input filename
|
|
size_t index; //current fv
|
|
//map to store the result of the calculation
|
|
std::map<std::string, std::vector< std::vector<Real> > > m;
|
|
//iterator for the map
|
|
std::map<std::string, std::vector< std::vector<Real> > >::iterator it;
|
|
|
|
};
|
|
|
|
int getfvectorsVM ( Word *args , Word &result , int message,
|
|
Word &local , Supplier s ){
|
|
getFVectorsLI* li = ( getFVectorsLI*) local.addr ;
|
|
switch ( message ){
|
|
case OPEN : if (li) {
|
|
delete li;
|
|
}
|
|
local.addr = new getFVectorsLI ( ( FText*) args[0].addr );
|
|
return 0;
|
|
|
|
case REQUEST : result.addr = li ? li->getNext():0;
|
|
return result.addr ? YIELD : CANCEL ;
|
|
case CLOSE : if (li){
|
|
delete li;
|
|
local.addr = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// A2.3 Specification of operator ~getfvectors~
|
|
|
|
OperatorSpec getfvectorsSpec(
|
|
" text int -> stream(fvector",
|
|
" _ getfvectors[_]",
|
|
" calculates the fvectors of a mp3-file",
|
|
" query filename getfvectors[1]"
|
|
);
|
|
|
|
|
|
// A2.4 Create instance of operator ~getfvectors~
|
|
|
|
Operator getfvectorsOP (
|
|
"getfvectors",
|
|
getfvectorsSpec.getStr(),
|
|
getfvectorsVM,
|
|
Operator::SimpleSelect,
|
|
getfvectorsTM
|
|
);
|
|
|
|
/*
|
|
2 Type Constructor ~mp3~
|
|
|
|
2.1 Class ~MP3~
|
|
|
|
*/
|
|
class MP3 : public Attribute {
|
|
public:
|
|
/* Constructor. Initalizes the DBArray mp3Data. */
|
|
MP3() {};
|
|
MP3( const int size );
|
|
/* Destructor. Destroys the DBArray. */
|
|
~MP3();
|
|
/* This object can be deleted by the Secondo system. */
|
|
void Destroy();
|
|
|
|
/* Returns the object's hash value. */
|
|
size_t HashValue() const;
|
|
/* Copy the data of another MP3 object into this object. */
|
|
void CopyFrom(const Attribute* right);
|
|
/* Compare this MP3 with another MP3 object. */
|
|
int Compare(const Attribute * arg) const;
|
|
/* Adjacent is not useful for MP3 */
|
|
bool Adjacent(const Attribute * arg) const;
|
|
/* Clones this MP3 object. */
|
|
MP3* Clone() const;
|
|
/* Prints a textual representation of an MP3 file. */
|
|
ostream& Print( ostream &os ) const;
|
|
/* returns the number of FLOBs. Always 1. */
|
|
int NumOfFLOBs() const;
|
|
/* Get the FLOB (DBArray) */
|
|
Flob *GetFLOB(const int i);
|
|
/* Returns the size of a class instance */
|
|
size_t Sizeof() const;
|
|
/* Returns a new MP3 object which contains the frames with indices
|
|
between begin and begin + size */
|
|
MP3* SubMP3(int begin, int size) const;
|
|
/* Returns the concatination of this MP3 and the other MP3 object. */
|
|
MP3* Concat (const MP3* other) const;
|
|
/* This methods writes the Base64 string of this MP3 Object into
|
|
the string textBytes. This string textBytes must be provided by
|
|
the caller. */
|
|
void Encode( string& textBytes ) const;
|
|
/* Gets the Base64 string and reconstruct the nessesary
|
|
DBArray for our MP3 song. Afterwards some attributes
|
|
of the MP3 song will be recalculated. */
|
|
void Decode( const string& textBytes );
|
|
/* Saves this MP3 song into the file with name filename. */
|
|
bool SaveMP3ToFile( const char *fileName ) const;
|
|
/* Restores the MP3 song from the file with name filename. */
|
|
bool LoadMP3FromFile( const string fileName );
|
|
/* Returns whether this MP3 has an ID3 tag. */
|
|
bool ExistsID() const;
|
|
/* Removes the ID3 tag from this MP3 if exists. */
|
|
void RemoveID();
|
|
/* Stores a new ID3 tag into this mp3. An older version will be
|
|
replaced. */
|
|
void PutID(char *iddump);
|
|
/* Returns whether this MP3 has lyrics. */
|
|
bool ExistsLyrics() const;
|
|
/* Returns the beginning of the first frame of the MP3
|
|
which is in the buffer. */
|
|
int FindFirstHeader(const char *buffer,int size) const;
|
|
/* Returns the beginning of the next frame of the MP3
|
|
which is in the buffer. The beginning of the previous
|
|
frame has to be provided in prevHeader. */
|
|
int FindNextHeader(int prevHeader, const char *buffer, int size) const;
|
|
/* Returns the length of the frame which begins at position
|
|
prevHeader in bytes. */
|
|
int FrameLength (int prevHeader, const char* buffer) const;
|
|
/* Calculates the MP3 version of the song which is in
|
|
the buffer. */
|
|
int CalcMP3Version(int Header, const char *buffer) const;
|
|
/* Calculates the bitrate of the song which is in
|
|
the buffer. */
|
|
int CalcBitrate(int Header, const char *buffer) const;
|
|
/* Calculates the frequency of the song which is in
|
|
the buffer. */
|
|
int CalcFrequency(int Header, const char *buffer) const;
|
|
/* Stores the ID3 dump (128 bytes) into buffer.
|
|
The buffer has to be provided by the caller. */
|
|
void GetID3Dump (char *buffer) const;
|
|
/* Stores the Lyrics dump into a buffer. The size
|
|
of the lyrics is unknown before and is returned
|
|
in the output argument ~lyricssize~. */
|
|
char* GetLyricsDump (int &lyricssize) const;
|
|
// OLD signature: void GetLyricsDump (const char **buffer, int &size) const;
|
|
/* Removes the lyrics from this object if Lyrics exists. */
|
|
void RemoveLyrics();
|
|
/* Stores the given lyrics into this MP3 object.
|
|
If this object has already lyrics this lycris will be
|
|
overwritten. */
|
|
void PutLyrics(char *lyricsdump, int size);
|
|
/* Returns the MP3 version of this object. (MPEG1 or MPEG2) */
|
|
int GetMP3Version() const;
|
|
/* Returns the bitrate of this MP3. */
|
|
int GetBitrate() const;
|
|
/* Returns the frequency of this MP3. */
|
|
int GetFrequency() const;
|
|
/* Returns the number of frames of this MP3. */
|
|
int GetFrameCount() const;
|
|
/* Returns the length of this MP3 in seconds. */
|
|
int GetLength() const;
|
|
|
|
static const string BasicType() { return "mp3"; }
|
|
static const bool checkType(const ListExpr type){
|
|
return listutils::isSymbol(type, BasicType());
|
|
}
|
|
//Returns the begin of a frame
|
|
int getFrameAdr(int index) const;
|
|
private:
|
|
|
|
Flob mp3Data;
|
|
DbArray<int> framesAdr;
|
|
|
|
int version;
|
|
int bitrate;
|
|
bool canDelete;
|
|
int length;
|
|
int framecount;
|
|
int frequency;
|
|
int lengthID3v2Tag;
|
|
};
|
|
|
|
/*
|
|
2.1.1 Constructor.
|
|
|
|
Initalizes the FLop and the DbArray.
|
|
|
|
*/
|
|
MP3::MP3(const int size) : mp3Data(size), framesAdr(0), canDelete(false) {
|
|
// empty.
|
|
}
|
|
|
|
/*
|
|
2.1.2 Destructor.
|
|
|
|
Destroys the Flop and DBArray.
|
|
|
|
*/
|
|
MP3::~MP3() {
|
|
if( canDelete ) {
|
|
mp3Data.destroy();
|
|
framesAdr.destroy();
|
|
}
|
|
}
|
|
|
|
/*
|
|
2.1.3 Destroy
|
|
|
|
This object can be deleted by the Secondo system.
|
|
|
|
*/
|
|
void MP3::Destroy() {
|
|
canDelete = true;
|
|
}
|
|
|
|
|
|
/*
|
|
2.1.7 HashValue
|
|
|
|
Calcules a hash value for an MP3.
|
|
|
|
*/
|
|
size_t MP3::HashValue() const {
|
|
if (!IsDefined())
|
|
return 0;
|
|
else
|
|
return mp3Data.getSize();
|
|
}
|
|
|
|
/*
|
|
2.1.8 CopyFrom
|
|
|
|
Copy the data of another MP3 object into this object.
|
|
|
|
*/
|
|
void MP3::CopyFrom(const Attribute* right) {
|
|
const MP3 *r = (const MP3 *)right;
|
|
|
|
if (r->mp3Data.getSize() > 0){
|
|
mp3Data.copyFrom(r->mp3Data);
|
|
}
|
|
if (r->framesAdr.getSize()> 0){
|
|
framesAdr.copyFrom(r->framesAdr);
|
|
}
|
|
version = r->version;
|
|
bitrate = r->bitrate;
|
|
canDelete = r->canDelete;
|
|
length = r->length;
|
|
framecount = r->framecount;
|
|
frequency = r->frequency;
|
|
SetDefined(r->IsDefined());
|
|
}
|
|
|
|
/*
|
|
2.1.9 Compare
|
|
|
|
Compare this MP3 with another MP3 object.
|
|
|
|
*/
|
|
int MP3::Compare(const Attribute * arg) const {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
2.1.10 Adjacent
|
|
|
|
Adjacent is not useful for MP3
|
|
|
|
*/
|
|
bool MP3::Adjacent(const Attribute * arg) const {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
2.1.11 Clone
|
|
|
|
Clones this MP3 object.
|
|
|
|
*/
|
|
MP3* MP3::Clone() const {
|
|
MP3 *newMP3 = new MP3( 0 );
|
|
newMP3->CopyFrom( this );
|
|
return newMP3;
|
|
}
|
|
|
|
/*
|
|
2.1.12 Print
|
|
|
|
prints a textual representation of an MP3 file.
|
|
|
|
*/
|
|
ostream& MP3::Print( ostream &os ) const {
|
|
return os << "MP3 Algebra" << endl;
|
|
}
|
|
|
|
/*
|
|
2.1.13 NumOfFLOBs
|
|
|
|
returns the number of FLOBs. Always 1.
|
|
|
|
*/
|
|
int MP3::NumOfFLOBs() const {
|
|
return 2;
|
|
}
|
|
|
|
/*
|
|
2.1.14 GetFLOB
|
|
|
|
Get the FLOB (DBArray)
|
|
|
|
*/
|
|
Flob* MP3::GetFLOB(const int i) {
|
|
assert( i >= 0 && i < NumOfFLOBs() );
|
|
if (i==0) {
|
|
return &mp3Data;
|
|
}
|
|
else {
|
|
return &framesAdr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
2.1.14 Sizeof
|
|
|
|
Returns the size of a class instance
|
|
|
|
*/
|
|
size_t MP3::Sizeof() const {
|
|
return sizeof(*this);
|
|
}
|
|
|
|
/*
|
|
2.1.15 SubMP3
|
|
|
|
Returns a new MP3 object which contains the frames with indices
|
|
between begin and begin + size.
|
|
|
|
*/
|
|
int MP3::getFrameAdr(int index) const{
|
|
assert (index<framesAdr.Size());
|
|
int elem;
|
|
if (framesAdr.Get(index,&elem)) {
|
|
return elem;
|
|
}
|
|
else { //should not happen
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
MP3* MP3::SubMP3(int beginFrame, int size) const {
|
|
MP3 *newmp3;
|
|
|
|
if (!this->IsDefined()){
|
|
/* If this MP3 object is undefined all parts of this
|
|
MP3 are also undefined. Therefore we return a
|
|
clone of this. */
|
|
newmp3 = this->Clone();
|
|
return newmp3;
|
|
}
|
|
|
|
if (beginFrame >= this->GetFrameCount()){
|
|
// the beginning is behind the end, an undefined mp3
|
|
// can be returnend
|
|
newmp3 = new MP3(0);
|
|
newmp3->SetDefined (false);
|
|
return newmp3;
|
|
}
|
|
|
|
if (beginFrame < 0){
|
|
// beginframe was in front of the beginning of a song,
|
|
// the parameters have to be adjusted
|
|
size = size + beginFrame;
|
|
beginFrame = 0;
|
|
}
|
|
|
|
if ( (beginFrame + size) > GetFrameCount()){
|
|
size = GetFrameCount() - beginFrame;
|
|
}
|
|
|
|
if (size <= 0){
|
|
// size was <=0, an undefined mp3 can be returned
|
|
newmp3 = new MP3(0);
|
|
newmp3->SetDefined (false);
|
|
return newmp3;
|
|
}
|
|
|
|
//create new MP3
|
|
newmp3 = new MP3(0);
|
|
|
|
int sizeOfId3v1Tag = 128;
|
|
int sizeOfId3v2Tag = this->lengthID3v2Tag;
|
|
|
|
//calculate cut-positions;
|
|
int cutPos1,cutPos2;
|
|
|
|
//first frame begins after Id3v2Tag
|
|
if (beginFrame==0) {
|
|
cutPos1=sizeOfId3v2Tag;
|
|
}//first frame begins within the mp3
|
|
else {
|
|
cutPos1=getFrameAdr(beginFrame);
|
|
}//last frame is endframe
|
|
if (beginFrame+size==GetFrameCount()){
|
|
cutPos2=mp3Data.getSize()-sizeOfId3v1Tag-1;
|
|
}//last frame is within mp3
|
|
else {
|
|
cutPos2=getFrameAdr(beginFrame+size)-1;
|
|
}
|
|
int sizeOfFrames=(cutPos2-cutPos1)+1;
|
|
|
|
// calculate size of new MP3
|
|
int sizeOfNewMP3 = sizeOfFrames+sizeOfId3v2Tag;
|
|
if (ExistsID()) {
|
|
sizeOfNewMP3 += sizeOfId3v1Tag;
|
|
}
|
|
// resize FLOB
|
|
newmp3->mp3Data.resize(sizeOfNewMP3);
|
|
|
|
// copy 1. ID3v2Tag 2. sub Frames and 3. ID3v1Tag
|
|
|
|
//1. create buffer for ID3v2Tag, read and copy data into new Flob
|
|
char* bufferId3v2Tag = new char[sizeOfId3v2Tag];
|
|
mp3Data.read(bufferId3v2Tag,sizeOfId3v2Tag,0);
|
|
newmp3->mp3Data.write(bufferId3v2Tag,sizeOfId3v2Tag,0);
|
|
delete bufferId3v2Tag;
|
|
|
|
//2. create buffer for Frames, read and copy data into new Flob
|
|
char* bufferFrames = new char[sizeOfFrames];
|
|
// calculate cut-position
|
|
mp3Data.read(bufferFrames,sizeOfFrames,cutPos1);
|
|
newmp3->mp3Data.write(bufferFrames,sizeOfFrames,sizeOfId3v2Tag);
|
|
delete bufferFrames;
|
|
|
|
//3. create buffer for Id3v1, read and copy data into new Flob
|
|
if (ExistsID()){
|
|
char* bufferId3v1Tag =new char[sizeOfId3v1Tag];
|
|
mp3Data.read(bufferId3v1Tag,sizeOfId3v1Tag,
|
|
this->mp3Data.getSize()-128 );
|
|
newmp3->mp3Data.write(bufferId3v1Tag,sizeOfId3v1Tag,
|
|
sizeOfNewMP3-sizeOfId3v1Tag);
|
|
delete bufferId3v1Tag;
|
|
|
|
}
|
|
|
|
// recalculate the attributes of our new mp3.
|
|
newmp3->version = this->version;
|
|
newmp3->bitrate = this->bitrate;
|
|
newmp3->canDelete = this->canDelete;
|
|
newmp3->length = (int) (size * 1152.0 / ((float) this->frequency));
|
|
newmp3->framecount = size;
|
|
newmp3->frequency = this->frequency;
|
|
newmp3->SetDefined (true);
|
|
newmp3->lengthID3v2Tag = sizeOfId3v2Tag;
|
|
|
|
//adjust the begin adress of the frames an copy
|
|
int adjust=cutPos1-sizeOfId3v2Tag;
|
|
for (int i=beginFrame; i<beginFrame+size;i++){
|
|
newmp3->framesAdr.Append(getFrameAdr(i)-adjust);
|
|
}
|
|
return newmp3;
|
|
|
|
}
|
|
|
|
/*
|
|
2.1.16 Concat
|
|
|
|
Returns the concatination of this MP3 and the other MP3 object.
|
|
|
|
*/
|
|
MP3* MP3::Concat (const MP3* other) const {
|
|
MP3* newmp3 = new MP3(0);
|
|
|
|
/* if the other mp3 is undefined we can just clone
|
|
this object and return the result */
|
|
if ( !other->IsDefined()){
|
|
newmp3 = this->Clone();
|
|
return newmp3;
|
|
}
|
|
|
|
/* if this object is not defined we can just return
|
|
the clone of the other object*/
|
|
if ( !this->IsDefined()){
|
|
newmp3 = other->Clone();
|
|
return newmp3;
|
|
}
|
|
|
|
/* First we copy both MP3s into the memory. */
|
|
int sizeofthisflob = mp3Data.getSize(),
|
|
sizeofotherflob = other->mp3Data.getSize();
|
|
|
|
char* thisbin = new char[sizeofthisflob];
|
|
char* otherbin = new char[sizeofotherflob];
|
|
|
|
|
|
mp3Data.read( thisbin, sizeofthisflob, 0 );
|
|
other->mp3Data.read( otherbin, sizeofotherflob, 0 );
|
|
|
|
/* We have to check weather the first MP3 has an ID3 tag. This
|
|
ID3 tag will be the one of the result. */
|
|
int sizeofid3v2 = FindFirstHeader(thisbin,sizeofthisflob);
|
|
|
|
/* The second MP3 might also contain an ID3v2 tag. Therefore
|
|
we have to find the first frame header. */
|
|
int beginotherheaderpos = FindFirstHeader(otherbin,sizeofotherflob);
|
|
|
|
int endofthisflob=this->getFrameAdr(this->framecount -1);
|
|
int newsize
|
|
= endofthisflob
|
|
// All frames except of the last one plus header
|
|
+ FrameLength(endofthisflob, thisbin)
|
|
// The length of the last frame
|
|
+ sizeofotherflob
|
|
// Just the size of the whole MP3 song
|
|
- beginotherheaderpos;
|
|
// except the size of the second Id3v2Tag
|
|
|
|
newmp3->mp3Data.resize(newsize);
|
|
/* Copy the first MP3 into the result MP3. */
|
|
newmp3->mp3Data.write( thisbin,
|
|
endofthisflob+ FrameLength(endofthisflob, thisbin),0);
|
|
/* Copy the second MP3 into the result MP3. */
|
|
newmp3->mp3Data.write( otherbin + beginotherheaderpos,
|
|
sizeofotherflob - beginotherheaderpos,
|
|
endofthisflob+ FrameLength(endofthisflob, thisbin));
|
|
|
|
/* recalculate the attributes of our new mp3. */
|
|
// pay attention MP3 might have different bitrates
|
|
newmp3->version = this->version;
|
|
newmp3->bitrate = this->bitrate;
|
|
newmp3->canDelete = this->canDelete;
|
|
newmp3->length = this->length + other->length;
|
|
newmp3->framecount = this->framecount + other->framecount;
|
|
newmp3->frequency = this->frequency;
|
|
newmp3->lengthID3v2Tag = sizeofid3v2;
|
|
newmp3->SetDefined (true);
|
|
|
|
//copy both frame address Arrays
|
|
for (int i=0; i < this->framecount; i++){
|
|
newmp3->framesAdr.Append(this->getFrameAdr(i));
|
|
}
|
|
|
|
//second indices must be adjusted
|
|
int adjust = endofthisflob+FrameLength(endofthisflob, thisbin)-
|
|
beginotherheaderpos;
|
|
for (int i=0; i < other->framecount; i++){
|
|
newmp3->framesAdr.Append( other->getFrameAdr(i)+adjust);
|
|
}
|
|
|
|
|
|
delete[] thisbin;
|
|
delete[] otherbin;
|
|
|
|
return newmp3;
|
|
}
|
|
|
|
|
|
/*
|
|
2.1.17 Encode
|
|
|
|
This methods writes the Base64 string of this MP3 Object into
|
|
the string textBytes. This string textBytes must be provided by
|
|
the caller.
|
|
|
|
*/
|
|
void MP3::Encode(string& textBytes) const {
|
|
Base64 b;
|
|
// allocate as many bytes as the size of the FLOB has
|
|
// for the data of the DBArray.
|
|
char* bytes = new char[mp3Data.getSize()];
|
|
// load the contents of the DBArray mp3Data into above
|
|
// byte array.
|
|
mp3Data.read( bytes, mp3Data.getSize(), 0);
|
|
// Write the encoded string into textBytes
|
|
b.encode( bytes, mp3Data.getSize(), textBytes );
|
|
delete[] bytes;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
2.1.18 Decode
|
|
|
|
Gets the Base64 string and reconstruct the nessesary
|
|
DBArray for our MP3 song. Afterwards some attributes
|
|
of the MP3 song will be recalculated.
|
|
|
|
*/
|
|
void MP3::Decode( const string& textBytes ) {
|
|
Base64 b;
|
|
// int previousheaderpos;
|
|
int sizeDecoded = b.sizeDecoded( textBytes.size() );
|
|
char *bytes = (char *)malloc( sizeDecoded );
|
|
|
|
int result = b.decode( textBytes, bytes );
|
|
|
|
assert( result <= sizeDecoded );
|
|
|
|
/* int headerpos = FindFirstHeader (bytes,result);
|
|
|
|
bitrate = CalcBitrate(headerpos, bytes);
|
|
frequency = CalcFrequency(headerpos, bytes);
|
|
version = CalcMP3Version(headerpos, bytes);
|
|
|
|
framecount = 1;
|
|
// We have to scan through the MP3 file in order to
|
|
// calculate some general value like length, number
|
|
// of frames, etc.
|
|
while ((headerpos >= 0) && (headerpos < (result - 4))) {
|
|
previousheaderpos = headerpos;
|
|
headerpos=FindNextHeader(headerpos, bytes, result);
|
|
if (headerpos >= 0) {
|
|
framecount++;
|
|
}
|
|
}
|
|
length = (int)(framecount * 1152.0 / ((float) frequency)); */
|
|
|
|
mp3Data.resize( result );
|
|
mp3Data.write(bytes, result, 0);
|
|
|
|
free( bytes );
|
|
}
|
|
|
|
/*
|
|
2.1.19 GetID3Dump
|
|
|
|
Stores the ID3 dump (128 bytes) into buffer.
|
|
The buffer has to be provided by the caller.
|
|
|
|
*/
|
|
void MP3::GetID3Dump (char *buffer) const {
|
|
mp3Data.read(buffer, 128 , mp3Data.getSize() - 128);
|
|
}
|
|
|
|
/*
|
|
2.1.20 ExistsID
|
|
|
|
Returns whether this MP3 has an ID3 tag.
|
|
|
|
*/
|
|
bool MP3::ExistsID() const {
|
|
char idbytes[128];
|
|
mp3Data.read(idbytes, 128, mp3Data.getSize()-128);
|
|
char tag[4] = "TAG";
|
|
return (strncmp (idbytes,tag,3) == 0);
|
|
}
|
|
|
|
/*
|
|
2.1.21 GetLyricsDump
|
|
|
|
Stores the Lyrics dump into a buffer. The size
|
|
of the lyrics is unknown before and is returned
|
|
in the output argument ~lyricssize~.
|
|
|
|
Allocates a buffer and returns a pointer to it.
|
|
The buffer must be deallocated with ~delete[]~ by the caller!
|
|
|
|
*/
|
|
char* MP3::GetLyricsDump (int &lyricssize) const {
|
|
/* The last 9 bytes of a Lyrics tag have to be
|
|
"LYRICS200" (= 9 bytes). The preceding six bytes
|
|
before contain the size of the whole lyrics tag
|
|
as a char array of decimals. */
|
|
char auxlyricssize_[15];
|
|
bool idexists=ExistsID();
|
|
/* The position of the lyrics tag depends on the existence
|
|
of the ID3 tag. */
|
|
if (idexists) {
|
|
/* length of ID3 = 128 */
|
|
/* length("LYRICS200") + length(lyricssize_) = 15 */
|
|
mp3Data.read(auxlyricssize_, 15,mp3Data.getSize() - 128 - 15);
|
|
} else {
|
|
mp3Data.read(auxlyricssize_, 15, mp3Data.getSize() - 15);
|
|
}
|
|
|
|
char lyricssize_[7];
|
|
memcpy( lyricssize_, auxlyricssize_, 6 );
|
|
lyricssize_[6] = 0;
|
|
lyricssize = atoi(lyricssize_);
|
|
|
|
/* Now we can copy the content of the lyrics into buffer.
|
|
Again this operation depends on the existence of an ID3 tag. */
|
|
char* result = new char[lyricssize];
|
|
if (idexists) {
|
|
/* length of ID3 = 128 */
|
|
/* length("LYRICS200") + length(lyricssize_) = 15 */
|
|
mp3Data.read(result, lyricssize, mp3Data.getSize()-128-15-lyricssize);
|
|
} else {
|
|
mp3Data.read(result, lyricssize, mp3Data.getSize()-15-lyricssize);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
2.1.22 ExistsLyrics
|
|
|
|
Returns whether this MP3 has lyrics.
|
|
|
|
*/
|
|
bool MP3::ExistsLyrics() const {
|
|
/* The last nine bytes of the lyrics always contain "LYRICS200". */
|
|
char bytes[10];
|
|
bytes[9] = 0;
|
|
|
|
/* The position of the lyrics depends on the existence of an ID3 tag. */
|
|
if (ExistsID() ) {
|
|
/* length of ID3 = 128 */
|
|
mp3Data.read(bytes, 9, mp3Data.getSize()-128-9);
|
|
} else {
|
|
/* length of ID3 = 128 */
|
|
mp3Data.read(bytes, 9, mp3Data.getSize()-9);
|
|
}
|
|
|
|
return (strncmp (bytes,"LYRICS200",9) == 0 );
|
|
}
|
|
|
|
/*
|
|
2.1.23 RemoveLyrics
|
|
|
|
Removes the lyrics from this object if Lyrics exists.
|
|
|
|
*/
|
|
void MP3::RemoveLyrics() {
|
|
if (!ExistsLyrics()) {
|
|
return;
|
|
}
|
|
|
|
int oldmp3size = mp3Data.getSize();
|
|
char* bytes = new char[oldmp3size];
|
|
mp3Data.read(bytes, oldmp3size, 0);
|
|
|
|
/* The last 9 bytes of a Lyrics tag have to be
|
|
"LYRICS200" (= 9 bytes). The preceding six bytes
|
|
before contain the size of the whole lyrics tag
|
|
as a char array of decimals. */
|
|
|
|
bool idexists = ExistsID();
|
|
|
|
/* The position of the lyrics tag depends on the existence
|
|
of the ID3 tag. */
|
|
char auxlyricssize_[15];
|
|
if (idexists) {
|
|
/* length of ID3 = 128 */
|
|
/* length("LYRICS200") + length(lyricssize_) = 15 */
|
|
mp3Data.read(auxlyricssize_, 15, oldmp3size-128-15);
|
|
} else {
|
|
mp3Data.read(auxlyricssize_, 15, oldmp3size-15);
|
|
}
|
|
char lyricssize_[7];
|
|
strncpy( lyricssize_, auxlyricssize_, 6 );
|
|
lyricssize_[6] = 0;
|
|
int lyricssize = atoi (lyricssize_) + 15;
|
|
|
|
int newsize=oldmp3size-lyricssize;
|
|
|
|
mp3Data.resize (newsize);
|
|
if (idexists) {
|
|
/* The ID3 tag exists. Therefor we have to copy
|
|
the proper MP3 data and the ID3 data into the
|
|
new MP3 object. */
|
|
mp3Data.write(bytes, newsize-128, 0); /* proper music data */
|
|
mp3Data.write(bytes+oldmp3size-128, 128, newsize-128); /* ID3 */
|
|
}
|
|
else {
|
|
mp3Data.write(bytes, newsize, 0); /* proper music data */
|
|
}
|
|
delete[] bytes;
|
|
}
|
|
|
|
/*
|
|
2.1.24 PutLyrics
|
|
|
|
Stores the given lyrics into this MP3 object.
|
|
If this object has already lyrics this lycris will be
|
|
overwritten.
|
|
|
|
*/
|
|
void MP3::PutLyrics(char *lyricsdump, int size) {
|
|
if (ExistsLyrics()) {
|
|
/* Remove the old Lyrics tag if exists. */
|
|
RemoveLyrics();
|
|
}
|
|
|
|
int oldmp3size = mp3Data.getSize();
|
|
bool existsid = ExistsID();
|
|
char* bytes = new char[oldmp3size];
|
|
|
|
mp3Data.read(bytes, oldmp3size, 0);
|
|
|
|
mp3Data.resize(oldmp3size+size);
|
|
|
|
if (existsid) {
|
|
/* Store the music data into mp3Data without ID3 and lyrics
|
|
tag. The lyrics was deleted before! */
|
|
/* length of ID3 = 128 */
|
|
mp3Data.write(bytes, oldmp3size-128, 0);
|
|
/* Store the (new) lyrics into mp3Data. */
|
|
mp3Data.write(lyricsdump, size, oldmp3size-128);
|
|
/* Store the ID3 tag. */
|
|
mp3Data.write(bytes+oldmp3size-128, 128, oldmp3size-128+size);
|
|
} else {
|
|
/* Store the music data into mp3Data without ID3 and lyrics
|
|
tag. The lyrics was deleted before! */
|
|
mp3Data.write(bytes, oldmp3size, 0);
|
|
/* Store the (new) lyrics into mp3Data. */
|
|
mp3Data.write(lyricsdump, size, oldmp3size);
|
|
/* no ID3 information available. */
|
|
}
|
|
delete[] bytes;
|
|
}
|
|
|
|
/*
|
|
2.1.25 RemoveID
|
|
|
|
Removes the ID3 tag from this MP3 if exists.
|
|
|
|
*/
|
|
void MP3::RemoveID () {
|
|
if ( !ExistsID() ) {
|
|
return;
|
|
}
|
|
/* copy mp3Data into bytes */
|
|
int oldmp3size = mp3Data.getSize();
|
|
char* bytes = new char[oldmp3size];
|
|
mp3Data.read(bytes, oldmp3size, 0 );
|
|
|
|
/* length of ID3 = 128 */
|
|
int newsize = oldmp3size-128;
|
|
mp3Data.resize (newsize);
|
|
|
|
/* Write the music data into mp3Data. */
|
|
mp3Data.write(bytes,newsize,0);
|
|
delete[] bytes;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
2.1.26 PutID
|
|
|
|
Stores a new ID3 tag into this mp3. An older version will be
|
|
replaced.
|
|
|
|
*/
|
|
void MP3::PutID (char *iddump) {
|
|
if ( !ExistsID()) {
|
|
/* length of ID3 = 128 */
|
|
mp3Data.resize (mp3Data.getSize()+128);
|
|
}
|
|
mp3Data.write(iddump, 128, mp3Data.getSize()-128);
|
|
}
|
|
|
|
/*
|
|
2.1.27 SaveMP3ToFile
|
|
|
|
Saves this MP3 song into the file with name filename.
|
|
|
|
*/
|
|
bool MP3::SaveMP3ToFile( const char *fileName ) const {
|
|
FILE *f = fopen( fileName, "wb" );
|
|
|
|
if( f == NULL )
|
|
return false;
|
|
|
|
char* bytes = new char[mp3Data.getSize()];
|
|
mp3Data.read( bytes, mp3Data.getSize(), 0);
|
|
|
|
if( fwrite( bytes, 1, mp3Data.getSize(), f ) != mp3Data.getSize() )
|
|
return false;
|
|
|
|
fclose( f );
|
|
delete bytes;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
2.1.28 LoadMP3FromFile
|
|
|
|
Restores the MP3 song from the file with name filename.
|
|
|
|
*/
|
|
bool MP3::LoadMP3FromFile( const string fileName ) {
|
|
// check for valid fileName
|
|
if( fileName == ""
|
|
|| !FileSystem::FileOrFolderExists(fileName)
|
|
|| FileSystem::IsDirectory(fileName) ){
|
|
cerr << "loadmp3: Cannot open file '" << fileName << "'!" << endl;
|
|
SetDefined( false );
|
|
return false;
|
|
}
|
|
|
|
// get file size
|
|
int32_t filesize = FileSystem::GetFileSize(fileName);
|
|
if( filesize<0 ){
|
|
cerr << "loadmp3: Filesize to small for '" << fileName << "'!" << endl;
|
|
SetDefined( false );
|
|
return false;
|
|
}
|
|
|
|
// open file
|
|
ifstream file;
|
|
file.open(fileName.c_str(),ios::binary);
|
|
if(!file.good()){
|
|
cerr << "loadmp3: Error opening file '" << fileName << "'!" << endl;
|
|
SetDefined( false );
|
|
return false;
|
|
}
|
|
|
|
// read the file
|
|
char* bytes = new char[ filesize ];
|
|
file.seekg(0,ios::end);
|
|
// streampos fileend = file.tellg();
|
|
file.seekg(0,ios::beg);
|
|
file.read(bytes,filesize);
|
|
file.close();
|
|
|
|
int headerpos = FindFirstHeader (bytes,filesize);
|
|
|
|
lengthID3v2Tag = headerpos;
|
|
// framelength=FrameLength(headerpos,bytes);
|
|
framesAdr.Append(headerpos);
|
|
|
|
bitrate = CalcBitrate(headerpos, bytes);
|
|
frequency = CalcFrequency(headerpos, bytes);
|
|
version = CalcMP3Version(headerpos, bytes);
|
|
|
|
framecount = 1;
|
|
/* We have to scan through the MP3 file in order to
|
|
calculate some general value like length, number
|
|
of frames, etc. */
|
|
while ((headerpos >= 0) && (headerpos < (filesize - 4))) {
|
|
headerpos=FindNextHeader(headerpos, bytes, filesize);
|
|
if (headerpos >= 0) {
|
|
framecount++;
|
|
framesAdr.Append(headerpos);
|
|
}
|
|
}
|
|
length = (int)(framecount * 1152.0 / ((float) frequency));
|
|
|
|
mp3Data.resize( filesize );
|
|
mp3Data.write(bytes, filesize, 0);
|
|
SetDefined( true );
|
|
delete[] bytes;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
2.1.28 FindFirstHeader
|
|
|
|
Returns the beginning of the first frame of the MP3
|
|
which is in the buffer.
|
|
|
|
*/
|
|
int MP3::FindFirstHeader(const char *buffer, int size) const {
|
|
/* search for the beginning of the header... */
|
|
|
|
/* First we have to search for 0xFF, the synchronization
|
|
byte. This byte must be followed by one which highest
|
|
nibble is also F. To ensure that we catched a real
|
|
beginning of a frame we assume that this is correct
|
|
and calculate the beginning of the next header.
|
|
Only if this potential next header begins again with
|
|
FF this header is accepted. Otherwise we continue the
|
|
search. */
|
|
|
|
for (int i = 0; i < size - 3; i++) {
|
|
if ((buffer[i] == (char)0xFF) && ((buffer[i+1] & 0xF0) == 0xF0)) {
|
|
int offset2 = FindNextHeader (i, buffer, size);
|
|
if ( offset2 >=0 && buffer [offset2] == (char) 0xFF) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
2.1.29 FrameLength
|
|
|
|
Returns the length of the frame which begins at position
|
|
prevHeader in bytes.
|
|
|
|
*/
|
|
int MP3::FrameLength (int prevHeader, const char *buffer) const {
|
|
int bitrate = CalcBitrate (prevHeader, buffer)*1000;
|
|
int frequ = CalcFrequency (prevHeader, buffer);
|
|
int padding = (buffer[prevHeader + 2] & 0x02) >> 1;
|
|
return (144* bitrate / frequ + padding);
|
|
}
|
|
|
|
/*
|
|
2.1.30 FindNextHeader
|
|
|
|
Returns the beginning of the next frame of the MP3
|
|
which is in the buffer. The beginning of the previous
|
|
frame has to be provided in prevHeader.
|
|
|
|
*/
|
|
int MP3::FindNextHeader(int prevHeader, const char *buffer, int size) const {
|
|
int length = FrameLength (prevHeader, buffer);
|
|
|
|
/* The maximum frame length is 1440. */
|
|
if ((length < 0) || (length > 1440) || (length + prevHeader >= size) )
|
|
// length is out of range, so there is no next header
|
|
return -1;
|
|
else if ( buffer [prevHeader+length] != (char) 0xFF)
|
|
// the sync-Tag 00xFF was not found, so there is no next Header
|
|
return -1;
|
|
return prevHeader+length;
|
|
}
|
|
|
|
/*
|
|
2.1.31 CalcMP3Version
|
|
|
|
Calculates the MP3 version of the song which is in
|
|
the buffer.
|
|
|
|
*/
|
|
int MP3::CalcMP3Version(int Header, const char *buffer) const {
|
|
byte versionCode = buffer[Header + 1] & 0X08;
|
|
return (versionCode == 8) ? 1 : 2;
|
|
}
|
|
|
|
/*
|
|
2.1.32 CalcBitrate
|
|
|
|
Calculates the bitrate of the song which is in
|
|
the buffer.
|
|
|
|
*/
|
|
int MP3::CalcBitrate(int Header, const char *buffer) const {
|
|
byte bitrateCode = (buffer[Header + 2] & 0xF0) >> 4;
|
|
|
|
switch(bitrateCode) {
|
|
case 0: return -1;
|
|
case 1: return 32;
|
|
case 2: return 40;
|
|
case 3: return 48;
|
|
case 4: return 56;
|
|
case 5: return 64;
|
|
case 6: return 80;
|
|
case 7: return 96;
|
|
case 8: return 112;
|
|
case 9: return 128;
|
|
case 10: return 160;
|
|
case 11: return 192;
|
|
case 12: return 224;
|
|
case 13: return 256;
|
|
case 14: return 320;
|
|
case 15: return -1;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
2.1.33 CalcFrequency
|
|
|
|
Calculates the frequency of the song which is in
|
|
the buffer.
|
|
|
|
*/
|
|
int MP3::CalcFrequency(int Header, const char *buffer) const {
|
|
int frequencyCode = buffer[Header + 2] & 0x0C;
|
|
|
|
switch(frequencyCode) {
|
|
case 0: return 44100;
|
|
case 1: return 32000;
|
|
case 2: return 48000;
|
|
case 3: return -1;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
2.1.34 GetBitrate
|
|
|
|
Returns the bitrate of this MP3.
|
|
|
|
*/
|
|
int MP3::GetBitrate() const {
|
|
return bitrate;
|
|
}
|
|
|
|
/*
|
|
2.1.35 GetMP3Version
|
|
|
|
Returns the MP3 version of this object. (MPEG1 or MPEG2)
|
|
|
|
*/
|
|
int MP3::GetMP3Version() const {
|
|
return version;
|
|
}
|
|
|
|
/*
|
|
2.1.36 GetFrequency
|
|
|
|
Returns the frequency of this MP3.
|
|
|
|
*/
|
|
int MP3::GetFrequency() const {
|
|
return frequency;
|
|
}
|
|
|
|
/*
|
|
2.1.37 GetFrameCount
|
|
|
|
Returns the number of frames of this MP3.
|
|
|
|
*/
|
|
int MP3::GetFrameCount() const{
|
|
return framecount;
|
|
}
|
|
|
|
/*
|
|
2.1.38 GetLength
|
|
|
|
Returns the length of this MP3 in seconds.
|
|
|
|
*/
|
|
int MP3::GetLength() const {
|
|
return length;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
2.2 List Representation
|
|
|
|
The list representation of a ~mp3~ are
|
|
|
|
---- ( <file>filename</file---> )
|
|
----
|
|
|
|
and
|
|
|
|
---- ( <text>BASE64-Coding</text---> )
|
|
----
|
|
|
|
If first representation is used, then the contents of a file is read
|
|
into the second representation. This is done automatically by the
|
|
Secondo parser.
|
|
|
|
2.3 ~Out~-Function
|
|
|
|
*/
|
|
ListExpr OutMP3( ListExpr typeInfo, Word value ) {
|
|
ListExpr result = nl->TextAtom();
|
|
|
|
MP3 *mp3 = (MP3 *)value.addr;
|
|
string encoded;
|
|
|
|
if (!mp3->IsDefined()){
|
|
/* the mp3 is not defined, so we have to return a nested list
|
|
with the symbol atom "undef"*/
|
|
return (nl->SymbolAtom(Symbol::UNDEFINED()));
|
|
}
|
|
|
|
mp3->Encode( encoded );
|
|
nl->AppendText( result, encoded );
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
2.4 ~In~-Function
|
|
|
|
*/
|
|
Word InMP3(const ListExpr typeInfo, const ListExpr instance,
|
|
const int errorPos, ListExpr& errorInfo, bool& correct) {
|
|
MP3 *mp3 = new MP3( 0 );
|
|
|
|
|
|
/* we have to check whether the nested list contains
|
|
a valid mp3 object or the symbol atom "undef"*/
|
|
if (listutils::isSymbolUndefined( instance ) )
|
|
{
|
|
correct = true;
|
|
mp3->SetDefined (false);
|
|
return SetWord (mp3);
|
|
}
|
|
|
|
if (nl->IsAtom( instance ) &&
|
|
nl->AtomType( instance ) == TextType ) {
|
|
string encoded;
|
|
nl->Text2String( instance, encoded );
|
|
|
|
/* encoded contains now the Base64 Data of this MP3 object. */
|
|
|
|
mp3->Decode( encoded );
|
|
|
|
mp3->SetDefined (true);
|
|
correct = true;
|
|
return SetWord( mp3 );
|
|
}
|
|
correct = false;
|
|
delete mp3;
|
|
return SetWord( Address(0) );
|
|
}
|
|
|
|
/*
|
|
2.5 The ~Property~-function
|
|
|
|
*/
|
|
ListExpr MP3Property() {
|
|
return
|
|
(nl->TwoElemList(
|
|
nl->FiveElemList(nl->StringAtom("Signature"),
|
|
nl->StringAtom("Example Type List"),
|
|
nl->StringAtom("List Rep"),
|
|
nl->StringAtom("Example List"),
|
|
nl->StringAtom("Remarks")),
|
|
nl->FiveElemList(nl->StringAtom("-> DATA"),
|
|
nl->StringAtom(MP3::BasicType()),
|
|
nl->StringAtom("( <file>filename</file---> )"),
|
|
nl->StringAtom("( <file>song.mp3</file---> )"),
|
|
nl->StringAtom(""))));
|
|
}
|
|
|
|
/*
|
|
2.6 ~Create~-function
|
|
|
|
*/
|
|
Word CreateMP3( const ListExpr typeInfo ) {
|
|
return SetWord( new MP3( 0 ) );
|
|
}
|
|
|
|
/*
|
|
2.7 ~Delete~-function
|
|
|
|
*/
|
|
void DeleteMP3( const ListExpr typeInfo, Word& w ) {
|
|
MP3 *mp3 = (MP3 *)w.addr;
|
|
mp3->Destroy();
|
|
delete mp3;
|
|
w.addr = 0;
|
|
}
|
|
|
|
/*
|
|
2.8 ~Close~-function
|
|
|
|
*/
|
|
void CloseMP3( const ListExpr typeInfo, Word& w ) {
|
|
delete (MP3 *)w.addr;
|
|
w.addr = 0;
|
|
}
|
|
|
|
/*
|
|
2.9 ~Clone~-function
|
|
|
|
*/
|
|
Word CloneMP3( const ListExpr typeInfo, const Word& w ) {
|
|
return SetWord( ((MP3 *)w.addr)->Clone() );
|
|
}
|
|
|
|
/*
|
|
2.10 ~SizeOf~-function
|
|
|
|
*/
|
|
int SizeOfMP3() {
|
|
return sizeof(MP3);
|
|
}
|
|
|
|
/*
|
|
2.11 ~Cast~-function
|
|
|
|
*/
|
|
void* CastMP3( void* addr ) {
|
|
return new (addr) MP3;
|
|
}
|
|
|
|
/*
|
|
2.12 Kind Checking Function
|
|
|
|
This function checks whether
|
|
the type constructor
|
|
is applied correctly. Since
|
|
type constructor ~mp3~ does not
|
|
have arguments, this is trivial.
|
|
|
|
*/
|
|
bool CheckMP3( ListExpr type, ListExpr& errorInfo ) {
|
|
return (nl->IsEqual( type, MP3::BasicType() ));
|
|
}
|
|
|
|
/*
|
|
2.13 Creation of the Type Constructor Instance
|
|
|
|
*/
|
|
TypeConstructor mp3(
|
|
// name
|
|
MP3::BasicType(),
|
|
// property function describing signature
|
|
MP3Property,
|
|
// out function
|
|
OutMP3,
|
|
// in function
|
|
InMP3,
|
|
// SaveToList function
|
|
0,
|
|
// RestoreFromList function
|
|
0,
|
|
// Object creation
|
|
CreateMP3,
|
|
// Object deletion
|
|
DeleteMP3,
|
|
// object open
|
|
OpenAttribute<MP3>,
|
|
// object save
|
|
SaveAttribute<MP3>,
|
|
// object close
|
|
CloseMP3,
|
|
// object clone
|
|
CloneMP3,
|
|
// cast function
|
|
CastMP3,
|
|
// sizeof function
|
|
SizeOfMP3,
|
|
// kind checking function
|
|
CheckMP3 );
|
|
|
|
/*
|
|
3 Type Constructor ~ID3~
|
|
|
|
3.1 Class ~ID3~
|
|
|
|
The class ID3 encapulates an ID3 tag. Although an ID3 tag is a
|
|
part of an MP3 file this algebra provides a seperate storage of
|
|
such ID3 tags.
|
|
|
|
*/
|
|
class ID3 : public Attribute {
|
|
public:
|
|
/* Standard constructor. */
|
|
ID3();
|
|
/* This object can be deleted by the Secondo system. */
|
|
void Destroy();
|
|
/* Calcules a hash value for an ID3. */
|
|
size_t HashValue() const;
|
|
/* Copy the data of another ID3 object into this object. */
|
|
void CopyFrom(const Attribute* right);
|
|
/* Compare this ID3 with another ID3 object. */
|
|
int Compare(const Attribute * arg) const;
|
|
/* Adjacent is not useful for ID3 */
|
|
bool Adjacent(const Attribute * arg) const;
|
|
/* Clones this ID3 object. */
|
|
ID3* Clone() const;
|
|
/* Prints a textual representation of an ID3 tag. */
|
|
ostream& Print( ostream &os ) const;
|
|
/* Returns the size of a class instance */
|
|
size_t Sizeof() const;
|
|
/* Creates an ID3 dump (128 bytes) from this object
|
|
and stores it into iddump. The memory has to be
|
|
provided by the caller. */
|
|
void MakeID3Dump (char *iddump);
|
|
/* Returns the author. The memory has to be
|
|
provided by the caller. */
|
|
void GetAuthor (char *authorname);
|
|
/* Returns the title. The memory has to be
|
|
provided by the caller. */
|
|
void GetTitle (char *titlename);
|
|
/* Returns the album name The memory has to be
|
|
provided by the caller.*/
|
|
void GetAlbum(char *albumname);
|
|
/* Returns the comment. The memory has to be
|
|
provided by the caller.*/
|
|
void GetComment(char *commentname);
|
|
/* Returns the track number. */
|
|
int GetTrack ();
|
|
/* Returns the year. */
|
|
int GetYear ();
|
|
/* Retuns the genre. The memory has to be
|
|
provided by the caller. */
|
|
void GetGenre(char *genrename);
|
|
/* Extracts the genre name from the genre code. */
|
|
const char *GetGenreName(byte nr);
|
|
|
|
static const string BasicType() { return "id3"; }
|
|
static const bool checkType(const ListExpr type){
|
|
return listutils::isSymbol(type, BasicType());
|
|
}
|
|
|
|
char songname [31];
|
|
char author [31];
|
|
char album [31];
|
|
int year;
|
|
char comment [31];
|
|
int track;
|
|
char genre [21];
|
|
int version;
|
|
|
|
bool canDelete;
|
|
};
|
|
|
|
/*
|
|
3.1.1 Constructor
|
|
|
|
Standard constructor.
|
|
|
|
*/
|
|
ID3::ID3() : canDelete(false) {
|
|
//songname[0] = 01;
|
|
//author[0] = 0;
|
|
//album[0] = 0;
|
|
//year = 0;
|
|
//comment[0] = 0;
|
|
//track = 0;
|
|
//genre[0] = 0;
|
|
//version = 0;
|
|
//canDelete = false;
|
|
}
|
|
|
|
/*
|
|
3.1.2 Destroy
|
|
|
|
This object can be deleted by the Secondo system.
|
|
|
|
*/
|
|
void ID3::Destroy() {
|
|
canDelete = true;
|
|
}
|
|
|
|
|
|
/*
|
|
3.1.5 HashValue
|
|
|
|
Calcules a hash value for an ID3.
|
|
|
|
*/
|
|
size_t ID3::HashValue () const {
|
|
if (!IsDefined()){
|
|
return 0;
|
|
} else {
|
|
size_t result = 0;
|
|
|
|
for (int i = 0; i < 31; i++) result = result + (size_t)songname[i];
|
|
for (int i = 0; i < 31; i++) result = result + (size_t)author[i];
|
|
for (int i = 0; i < 31; i++) result = result + (size_t)album[i];
|
|
result = result + (size_t)year;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/*
|
|
3.1.6 CopyFrom
|
|
|
|
Copy the data of another ID3 object into this object.
|
|
|
|
*/
|
|
void ID3::CopyFrom(const Attribute* right) {
|
|
const ID3* id = (const ID3*) right;
|
|
*this = *id;
|
|
}
|
|
|
|
/*
|
|
3.1.7 Compare
|
|
|
|
Compare this ID3 with another ID3 object.
|
|
|
|
*/
|
|
int ID3::Compare (const Attribute * arg) const {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
3.1.8 Adjacent
|
|
|
|
Adjacent is not useful for ID3
|
|
|
|
*/
|
|
bool ID3::Adjacent (const Attribute * arg) const {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
3.1.9 Clone
|
|
|
|
Clones this ID3 object.
|
|
|
|
*/
|
|
ID3* ID3::Clone() const {
|
|
ID3 *newID3 = new ID3();
|
|
newID3->CopyFrom(this);
|
|
return newID3;
|
|
}
|
|
|
|
/*
|
|
3.1.10 Print
|
|
|
|
Prints a textual representation of an ID3 file.
|
|
|
|
*/
|
|
ostream& ID3::Print( ostream &os ) const {
|
|
|
|
return os << "ID3 tag." << endl;
|
|
}
|
|
|
|
/*
|
|
3.1.11 Sizeof
|
|
|
|
Returns the size of a class instance
|
|
|
|
*/
|
|
size_t ID3::Sizeof() const {
|
|
return sizeof(*this);
|
|
}
|
|
|
|
|
|
/*
|
|
3.1.11 MakeID3Dump
|
|
|
|
Creates an ID3 dump (128 bytes) from this object
|
|
and stores it into iddump. The memory has to be
|
|
provided by the caller.
|
|
|
|
*/
|
|
void ID3::MakeID3Dump (char *iddump) {
|
|
char tag[4] ="TAG";
|
|
|
|
strncpy (iddump,tag,3);
|
|
strncpy (iddump + 3, songname,30);
|
|
strncpy (iddump + 33, author,30);
|
|
strncpy (iddump + 63, album, 30);
|
|
strncpy (iddump + 97, comment,30);
|
|
|
|
if (version == 1)
|
|
{
|
|
// ID3-Tag Version 1.1, Byte before Tracknumber has to zero
|
|
*(iddump+125) = (byte) 0x00;
|
|
*(iddump+126) = (byte) track;
|
|
}
|
|
|
|
char year_ [6];
|
|
sprintf (year_, "%d",year);
|
|
strncpy (iddump + 93, year_, 4);
|
|
|
|
|
|
// bool found = false;
|
|
byte genrenumber = 127;
|
|
for (int i=0; i<= 127; i++)
|
|
{
|
|
if (strncmp (genre, GetGenreName (i),21) == 0 )
|
|
{
|
|
// found = true;
|
|
genrenumber = i;
|
|
}
|
|
}
|
|
|
|
*(iddump + 127) = (byte) genrenumber;
|
|
}
|
|
|
|
/*
|
|
3.1.12 GetAuthor
|
|
|
|
Returns the author. The memory has to be
|
|
provided by the caller.
|
|
|
|
*/
|
|
void ID3::GetAuthor(char *authorname) {
|
|
strncpy (authorname,author,31);
|
|
}
|
|
|
|
/*
|
|
3.1.13 GetTitle
|
|
|
|
Returns the title. The memory has to be
|
|
provided by the caller.
|
|
|
|
*/
|
|
void ID3::GetTitle(char *titlename) {
|
|
strncpy (titlename,songname,31);
|
|
}
|
|
|
|
/*
|
|
3.1.14 GetAlbum
|
|
|
|
Returns the album name. The memory has to be
|
|
provided by the caller.
|
|
|
|
*/
|
|
void ID3::GetAlbum(char *albumname) {
|
|
strncpy (albumname,album,31);
|
|
}
|
|
|
|
/*
|
|
3.1.15 GetComment
|
|
|
|
Returns the comment. The memory has to be
|
|
provided by the caller.
|
|
|
|
*/
|
|
void ID3::GetComment(char *commentname) {
|
|
if (version == 0){
|
|
strncpy (commentname,comment,31);
|
|
}
|
|
else
|
|
strncpy (commentname,comment,29);
|
|
}
|
|
|
|
/*
|
|
3.1.16 GetGenre
|
|
|
|
Returns the genre. The memory has to be
|
|
provided by the caller.
|
|
|
|
*/
|
|
void ID3::GetGenre(char *genrename) {
|
|
strncpy (genrename,genre,21);
|
|
}
|
|
|
|
/*
|
|
3.1.17 GetTrack
|
|
|
|
Returns the track number.
|
|
|
|
*/
|
|
int ID3::GetTrack() {
|
|
return track;
|
|
}
|
|
|
|
/*
|
|
3.1.18 GetYear
|
|
|
|
Returns the year.
|
|
|
|
*/
|
|
int ID3::GetYear() {
|
|
return this->year;
|
|
}
|
|
|
|
/*
|
|
3.1.19 GetGenreName
|
|
|
|
Extracts the genre name from the genre code.
|
|
|
|
*/
|
|
const char *ID3::GetGenreName(byte nr) {
|
|
switch(nr) {
|
|
case 0: return "Blues";
|
|
case 1: return "Classic Rock";
|
|
case 2: return "Country";
|
|
case 3: return "Dance";
|
|
case 4: return "Disco";
|
|
case 5: return "Funk";
|
|
case 6: return "Grunge";
|
|
case 7: return "Hip-Hop";
|
|
case 8: return "Jazz";
|
|
case 9: return "Metal";
|
|
case 10: return "New Age";
|
|
case 11: return "Oldies";
|
|
case 12: return "Other";
|
|
case 13: return "Pop";
|
|
case 14: return "R&B";
|
|
case 15: return "Rap";
|
|
case 16: return "Raggae";
|
|
case 17: return "Rock";
|
|
case 18: return "Techno";
|
|
case 19: return "Industrial";
|
|
case 20: return "Alternative";
|
|
case 21: return "Ska";
|
|
case 22: return "Death Metal";
|
|
case 23: return "Pranks";
|
|
case 24: return "Soundtrack";
|
|
case 25: return "Euro-Techno";
|
|
case 26: return "Ambient";
|
|
case 27: return "Trip-Hop";
|
|
case 28: return "Vocal";
|
|
case 29: return "Jazz+Funk";
|
|
case 30: return "Fusion";
|
|
case 31: return "Trance";
|
|
case 32: return "Classical";
|
|
case 33: return "Instrumental";
|
|
case 34: return "Acid";
|
|
case 35: return "House";
|
|
case 36: return "Game";
|
|
case 37: return "Sound Clip";
|
|
case 38: return "Gospel";
|
|
case 39: return "Noise";
|
|
case 40: return "Alt. Rock";
|
|
case 41: return "Bass";
|
|
case 42: return "Soul";
|
|
case 43: return "Punk";
|
|
case 44: return "Space";
|
|
case 45: return "Meditative";
|
|
case 46: return "Instrumental Pop";
|
|
case 47: return "Instrumental Rock";
|
|
case 48: return "Ethnic";
|
|
case 49: return "Gothie";
|
|
case 50: return "Darkwave";
|
|
case 51: return "Techno-Industrial";
|
|
case 52: return "Electronic";
|
|
case 53: return "Pop-Folk";
|
|
case 54: return "Eurodance";
|
|
case 55: return "Dream";
|
|
case 56: return "Southern Rock";
|
|
case 57: return "Comedy";
|
|
case 58: return "Cult";
|
|
case 59: return "Gangsta";
|
|
case 60: return "Top 40";
|
|
case 61: return "Christian Rap";
|
|
case 62: return "Pop/Funk";
|
|
case 63: return "Jungle";
|
|
case 64: return "Native American";
|
|
case 65: return "Cabaret";
|
|
case 66: return "New Wave";
|
|
case 67: return "Psychadelic";
|
|
case 68: return "Rave";
|
|
case 69: return "Showtunes";
|
|
case 70: return "Trailer";
|
|
case 71: return "Lo-Fi";
|
|
case 72: return "Tribal";
|
|
case 73: return "Acid Punk";
|
|
case 74: return "Acid Jazz";
|
|
case 75: return "Polka";
|
|
case 76: return "Retro";
|
|
case 77: return "Musical";
|
|
case 78: return "Rock & Roll";
|
|
case 79: return "Hard Rock";
|
|
case 80: return "Folk";
|
|
case 81: return "Folk/Rock";
|
|
case 82: return "National Folk";
|
|
case 83: return "Swing";
|
|
case 84: return "Fusion";
|
|
case 85: return "Bebop";
|
|
case 86: return "Latin";
|
|
case 87: return "Revival";
|
|
case 88: return "Celtic";
|
|
case 89: return "Bluegrass";
|
|
case 90: return "Avantgarde";
|
|
case 91: return "Gothic Rock";
|
|
case 92: return "Progressive Rock";
|
|
case 93: return "Psychedelic Rock";
|
|
case 94: return "Symphonic Rock";
|
|
case 95: return "Slow Rock";
|
|
case 96: return "Big Band";
|
|
case 97: return "Chorus";
|
|
case 98: return "Easy Listening";
|
|
case 99: return "Acoustic";
|
|
case 100: return "Humour";
|
|
case 101: return "Speech";
|
|
case 102: return "Chanson";
|
|
case 103: return "Opera";
|
|
case 104: return "Chamber Music";
|
|
case 105: return "Sonata";
|
|
case 106: return "Symphony";
|
|
case 107: return "Booty Bass";
|
|
case 108: return "Primus";
|
|
case 109: return "Porn Groove";
|
|
case 110: return "Satire";
|
|
case 111: return "Slow Jam";
|
|
case 112: return "Club";
|
|
case 113: return "Tango";
|
|
case 114: return "Samba";
|
|
case 115: return "Folklore";
|
|
default: return "--Unknown--";
|
|
}
|
|
}
|
|
|
|
/*
|
|
3.2 List Representation
|
|
|
|
The list representation of an ~id3~ are
|
|
|
|
---- ( string string string int string string ) (ID3v1.0)
|
|
----
|
|
|
|
or
|
|
|
|
---- ( string string string int string int string ) (ID3v1.1)
|
|
----
|
|
|
|
3.3 ~Out~-Function
|
|
|
|
*/
|
|
|
|
ListExpr OutID3( ListExpr typeInfo, Word value ) {
|
|
ID3 *id3 = static_cast<ID3*>(value.addr);
|
|
|
|
ListExpr result;
|
|
string songName(id3->songname);
|
|
string authorName(id3->author);
|
|
string albumName(id3->album);
|
|
string commentName(id3->comment);
|
|
string genreName(id3->genre);
|
|
|
|
|
|
if (!id3->IsDefined()) {
|
|
/* the ID3 tag is not defined, so we have to return
|
|
a nested list with the symbol atom "undef"*/
|
|
return (nl->SymbolAtom("undef"));
|
|
}
|
|
|
|
/* If ID3 tag version is 1.0 the ID3 information is coded into
|
|
six elements. The ID3 tag version 1.1 has one field more namely
|
|
one for the track number. */
|
|
else if (id3->version == 0) {
|
|
result = nl->SixElemList(
|
|
nl->StringAtom(songName),
|
|
nl->StringAtom(authorName),
|
|
nl->StringAtom(albumName),
|
|
nl->IntAtom(id3->year),
|
|
nl->StringAtom(commentName),
|
|
nl->StringAtom(genreName));
|
|
} else {
|
|
/* we build up a seven element list here. */
|
|
result = nl->SixElemList(
|
|
nl->StringAtom(authorName),
|
|
nl->StringAtom(albumName),
|
|
nl->IntAtom(id3->year),
|
|
nl->StringAtom(commentName),
|
|
nl->IntAtom(id3->track),
|
|
nl->StringAtom(genreName));
|
|
result = nl->Cons (nl->StringAtom (songName), result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
3.4 ~In~-Function
|
|
|
|
*/
|
|
Word InID3( const ListExpr typeInfo, const ListExpr instance,
|
|
const int errorPos, ListExpr& errorInfo, bool& correct ){
|
|
ID3 *id3 = new ID3 ();
|
|
|
|
|
|
/* we have to check whether the nested list contains a valid
|
|
id3 tag or the symbol atom "undef"*/
|
|
if (nl->IsAtom( instance )
|
|
&& nl->AtomType( instance ) == SymbolType
|
|
&& nl->SymbolValue( instance ) == "undef" ) {
|
|
correct = true;
|
|
id3->SetDefined (false);
|
|
return SetWord (id3);
|
|
}
|
|
|
|
/* If ID3 tag version is 1.0 the ID3 information is coded into
|
|
six elements. The ID3 tag version 1.1 has one field more namely
|
|
one for the track number. */
|
|
else if (nl->ListLength (instance) == 6
|
|
&& nl->IsAtom (nl->First( instance ))
|
|
&& nl->AtomType (nl->First( instance )) == StringType
|
|
&& nl->IsAtom (nl->Second( instance ))
|
|
&& nl->AtomType (nl->Second( instance )) == StringType
|
|
&& nl->IsAtom (nl->Third( instance ))
|
|
&& nl->AtomType (nl->Third( instance )) == StringType
|
|
&& nl->IsAtom (nl->Fourth( instance ))
|
|
&& nl->AtomType (nl->Fourth( instance )) == IntType
|
|
&& nl->IsAtom (nl->Fifth( instance ))
|
|
&& nl->AtomType (nl->Fifth( instance )) == StringType
|
|
&& nl->IsAtom (nl->Sixth( instance ))
|
|
&& nl->AtomType (nl->Sixth( instance )) == StringType) {
|
|
|
|
|
|
id3->version = 0;
|
|
string songName = nl->StringValue(nl->First(instance));
|
|
string authorName = nl->StringValue(nl->Second(instance));
|
|
string albumName = nl->StringValue(nl->Third(instance));
|
|
char* dummy = (char*) songName.c_str();
|
|
strncpy (id3->songname,dummy,30);
|
|
*(id3->songname+30) = (byte) 0x00;
|
|
dummy = (char*) authorName.c_str();
|
|
strncpy (id3->author,dummy,30);
|
|
*(id3->author+30) = (byte) 0x00;
|
|
dummy = (char*) albumName.c_str();
|
|
strncpy (id3->album,dummy,30);
|
|
*(id3->album+30) = (byte) 0x00;
|
|
|
|
id3->year = nl->IntValue(nl->Fourth(instance));
|
|
|
|
string commentName = nl->StringValue(nl->Fifth(instance));
|
|
dummy = (char*) commentName.c_str();
|
|
strncpy (id3->comment,dummy,30);
|
|
*(id3->comment+30) = (byte) 0x00;
|
|
|
|
string genreName = nl->StringValue(nl->Sixth(instance));
|
|
dummy = (char*) genreName.c_str();
|
|
strncpy (id3->genre,dummy,20);
|
|
*(id3->genre+20) = (byte) 0x00;
|
|
|
|
id3->SetDefined(true);
|
|
|
|
correct = true;
|
|
return SetWord (id3);
|
|
} else if (nl->ListLength (instance) == 7
|
|
&& nl->IsAtom (nl->First( instance ))
|
|
&& nl->AtomType (nl->First( instance )) == StringType
|
|
&& nl->IsAtom (nl->Second( instance ))
|
|
&& nl->AtomType (nl->Second( instance )) == StringType
|
|
&& nl->IsAtom (nl->Third( instance ))
|
|
&& nl->AtomType (nl->Third( instance )) == StringType
|
|
&& nl->IsAtom (nl->Fourth( instance ))
|
|
&& nl->AtomType (nl->Fourth( instance )) == IntType
|
|
&& nl->IsAtom (nl->Fifth( instance ))
|
|
&& nl->AtomType (nl->Fifth( instance )) == StringType
|
|
&& nl->IsAtom (nl->Sixth( instance ))
|
|
&& nl->AtomType (nl->Sixth( instance )) == IntType
|
|
&& nl->IsAtom (nl->Sixth (nl->Rest (instance)) )
|
|
&& nl->AtomType (nl->Sixth( nl->Rest (instance) ))
|
|
== StringType) {
|
|
|
|
id3->version = 1;
|
|
string songName = nl->StringValue(nl->First(instance));
|
|
string authorName = nl->StringValue(nl->Second(instance));
|
|
string albumName = nl->StringValue(nl->Third(instance));
|
|
char* dummy = (char*) songName.c_str();
|
|
strncpy (id3->songname,dummy,30);
|
|
*(id3->songname+30) = (byte) 0x00;
|
|
dummy = (char*) authorName.c_str();
|
|
strncpy (id3->author,dummy,30);
|
|
*(id3->author+30) = (byte) 0x00;
|
|
dummy = (char*) albumName.c_str();
|
|
strncpy (id3->album,dummy,30);
|
|
*(id3->album+30) = (byte) 0x00;
|
|
|
|
id3->year = nl->IntValue(nl->Fourth(instance));
|
|
|
|
string commentName = nl->StringValue(nl->Fifth(instance));
|
|
dummy = (char*) commentName.c_str();
|
|
strncpy (id3->comment,dummy,28);
|
|
*(id3->comment+28) = (byte) 0x00;
|
|
|
|
id3->track = nl->IntValue(nl->Sixth(instance));
|
|
|
|
string genreName = nl->StringValue(nl->Sixth(nl->Rest (instance)));
|
|
|
|
dummy = (char*) genreName.c_str();
|
|
strncpy (id3->genre,dummy,20);
|
|
*(id3->genre+20) = (byte) 0x00;
|
|
|
|
id3->SetDefined(true);
|
|
|
|
correct = true;
|
|
return SetWord (id3);
|
|
} else {
|
|
/* other versions of ID3 tags are not supported. */
|
|
correct = false;
|
|
delete id3;
|
|
return SetWord( Address(0));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
3.5 The ~Property~-function
|
|
|
|
*/
|
|
ListExpr ID3Property() {
|
|
return (nl->TwoElemList
|
|
(nl->FiveElemList
|
|
(nl->StringAtom("Signature"),
|
|
nl->StringAtom("Example Type List"),
|
|
nl->StringAtom("List Rep"),
|
|
nl->StringAtom("Example List"),
|
|
nl->StringAtom("Remarks")),
|
|
nl->FiveElemList
|
|
(nl->StringAtom("-> DATA"),
|
|
nl->StringAtom(ID3::BasicType()),
|
|
nl->TextAtom("( string string string int string string )"),
|
|
nl->TextAtom(
|
|
"( 'songname' 'author' 'album' 1984 'comment' 'Rock') "),
|
|
nl->StringAtom(""))));
|
|
}
|
|
|
|
/*
|
|
3.6 ~Create~-function
|
|
|
|
*/
|
|
Word CreateID3( const ListExpr typeInfo ) {
|
|
return SetWord( new ID3( ) );
|
|
}
|
|
|
|
/*
|
|
3.7 ~Delete~-function
|
|
|
|
*/
|
|
void DeleteID3( const ListExpr typeInfo, Word& w ) {
|
|
ID3 *id3 = (ID3 *)w.addr;
|
|
id3->Destroy();
|
|
delete id3;
|
|
w.addr = 0;
|
|
}
|
|
|
|
/*
|
|
3.8 ~Close~-function
|
|
|
|
*/
|
|
void CloseID3( const ListExpr typeInfo, Word& w ) {
|
|
delete (ID3 *)w.addr;
|
|
w.addr = 0;
|
|
}
|
|
|
|
/*
|
|
3.9 ~Clone~-function
|
|
|
|
*/
|
|
Word CloneID3( const ListExpr typeInfo, const Word& w ) {
|
|
return SetWord( ((ID3 *)w.addr)->Clone() );
|
|
}
|
|
|
|
/*
|
|
3.10 ~SizeOf~-function
|
|
|
|
*/
|
|
int SizeOfID3() {
|
|
return sizeof(ID3);
|
|
}
|
|
|
|
|
|
/*
|
|
3.11 ~Cast~-function
|
|
|
|
*/
|
|
void* CastID3( void* addr ) {
|
|
return new (addr) ID3;
|
|
}
|
|
|
|
/*
|
|
3.12 Kind Checking Function
|
|
|
|
This function checks whether the type constructor is applied correctly. Since
|
|
type constructor ~id3~ does not have arguments, this is trivial.
|
|
|
|
*/
|
|
bool CheckID3( ListExpr type, ListExpr& errorInfo ) {
|
|
return (nl->IsEqual( type, ID3::BasicType() ));
|
|
}
|
|
|
|
/*
|
|
3.13 Creation of the Type Constructor Instance
|
|
|
|
*/
|
|
TypeConstructor id3(
|
|
// name
|
|
ID3::BasicType(),
|
|
// property function describing the signature
|
|
ID3Property,
|
|
// Out function
|
|
OutID3,
|
|
// In function
|
|
InID3,
|
|
// SaveToList
|
|
0,
|
|
// RestoreFromList
|
|
0,
|
|
// object creation
|
|
CreateID3,
|
|
// object deletion
|
|
DeleteID3,
|
|
// object open
|
|
0,
|
|
// object save
|
|
0,
|
|
// object close
|
|
CloseID3,
|
|
// object clone
|
|
CloneID3,
|
|
// cast function
|
|
CastID3,
|
|
// sizeof function
|
|
SizeOfID3,
|
|
// kind checking function
|
|
CheckID3 );
|
|
|
|
|
|
/*
|
|
4 Type Constructor ~Lyrics~
|
|
|
|
4.1 struct Line
|
|
|
|
This class encapsulates a single line of song lyrics.
|
|
|
|
*/
|
|
struct Line {
|
|
Line () {}
|
|
Line (int sec, char* text) {
|
|
seconds = sec;
|
|
}
|
|
int seconds;
|
|
char textline[255];
|
|
|
|
size_t HashValue() const {
|
|
size_t result = 0;
|
|
for (int i = 0; i < 255; i++) { result = result + textline[i]; }
|
|
return result;
|
|
}
|
|
};
|
|
|
|
enum LyricsState { partial, complete};
|
|
|
|
/*
|
|
|
|
4.2 Class ~Lyrics~
|
|
|
|
The class Lyrics encapulates the lyrics of an MP3. Although the lyrics are a
|
|
part of an MP3 file this algebra provides a seperate storage of
|
|
such lyrics lines.
|
|
|
|
*/
|
|
class Lyrics : public Attribute {
|
|
public:
|
|
/* Constructor */
|
|
Lyrics() {};
|
|
/* Constructor with size */
|
|
Lyrics( const int size);
|
|
/* Destructor */
|
|
~Lyrics();
|
|
/* Clones this Lyrics object. */
|
|
Lyrics *Clone() const;
|
|
/* Calcules a hash value for an Lyrics. */
|
|
size_t HashValue() const;
|
|
/* Copy the data of another Lyrics object into this object. */
|
|
void CopyFrom(const Attribute* right);
|
|
/* Returns the number of FLOBs : 1 */
|
|
int NumOfFLOBs() const;
|
|
/* Returns the line array. */
|
|
Flob *GetFLOB(const int i);
|
|
/* Compare this Lyrics with another Lyrics object. */
|
|
int Compare(const Attribute * arg) const;
|
|
/* Adjacent is not useful for Lyrics */
|
|
bool Adjacent(const Attribute * arg) const;
|
|
/* Returns the size of a class instance */
|
|
size_t Sizeof() const;
|
|
/* Appends a new line to the lines array. */
|
|
void Append( Line oneline);
|
|
/* This object can be deleted by the secondo system. */
|
|
void Destroy();
|
|
/* Returns the number of lines of the lyrics. */
|
|
int NoLines() const;
|
|
/* Returns the ith line of the lyrics. */
|
|
Line GetLine( int i ) const;
|
|
|
|
static const string BasicType() { return "lyrics"; }
|
|
static const bool checkType(const ListExpr type){
|
|
return listutils::isSymbol(type, BasicType());
|
|
}
|
|
|
|
private:
|
|
DbArray<Line> linearray;
|
|
bool canDelete;
|
|
};
|
|
|
|
|
|
/*
|
|
4.2.1 Constructors.
|
|
|
|
Constructor
|
|
|
|
*/
|
|
Lyrics::Lyrics(const int size) : linearray(size), canDelete(false) {
|
|
}
|
|
|
|
/*
|
|
|
|
4.2.2 Destructor.
|
|
|
|
*/
|
|
Lyrics::~Lyrics() {
|
|
if (canDelete) {
|
|
linearray.Destroy();
|
|
}
|
|
}
|
|
|
|
/*
|
|
4.2.3 Clone
|
|
|
|
Clones this Lyrics object.
|
|
|
|
*/
|
|
Lyrics *Lyrics::Clone() const {
|
|
Lyrics *lyrics = new Lyrics(0);
|
|
lyrics->CopyFrom(this);
|
|
return lyrics;
|
|
}
|
|
|
|
|
|
/*
|
|
4.2.6 HashValue
|
|
|
|
Calcules a hash value for an Lyrics.
|
|
|
|
*/
|
|
size_t Lyrics::HashValue () const {
|
|
size_t result = 0;
|
|
Line aLine;
|
|
for (int i = 0; i < linearray.Size(); i++) {
|
|
linearray.Get(i, aLine);
|
|
result += aLine.HashValue();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
4.2.7 CopyFrom
|
|
|
|
Copy the data of another Lyrics object into this object.
|
|
|
|
*/
|
|
void Lyrics::CopyFrom(const Attribute* right) {
|
|
const Lyrics* lyrics = (const Lyrics*) right;
|
|
|
|
for(int i = 0; i < lyrics->NoLines(); i++ ) {
|
|
Append(lyrics->GetLine(i));
|
|
}
|
|
SetDefined( lyrics->IsDefined() );
|
|
canDelete = lyrics->canDelete;
|
|
}
|
|
|
|
/*
|
|
4.2.8 NumOfFLOBs.
|
|
|
|
Returns the number of FLOBs : 1
|
|
|
|
*/
|
|
int Lyrics::NumOfFLOBs() const {
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
4.2.9 GetFLOB
|
|
|
|
Returns the line array.
|
|
|
|
*/
|
|
Flob *Lyrics::GetFLOB(const int i) {
|
|
return &linearray;
|
|
}
|
|
|
|
|
|
/*
|
|
4.2.10 Compare
|
|
|
|
Compare this Lyrics with another Lyrics object.
|
|
|
|
*/
|
|
int Lyrics::Compare (const Attribute * arg) const {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
4.2.11 Adjacent
|
|
|
|
Adjacent is not useful for Lyrics
|
|
|
|
*/
|
|
bool Lyrics::Adjacent (const Attribute * arg) const {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
4.2.12 Sizeof
|
|
|
|
Returns the size of a class instance
|
|
|
|
*/
|
|
size_t Lyrics::Sizeof() const {
|
|
return sizeof(*this);
|
|
}
|
|
|
|
/*
|
|
4.2.13 Append
|
|
|
|
Appends a new line to the lines array.
|
|
|
|
*/
|
|
void Lyrics::Append(Line oneline ) {
|
|
linearray.Append(oneline );
|
|
}
|
|
|
|
/*
|
|
4.2.14 Destroy
|
|
|
|
This object can be deleted by the secondo system.
|
|
|
|
*/
|
|
void Lyrics::Destroy() {
|
|
canDelete = true;
|
|
}
|
|
|
|
|
|
/*
|
|
4.2.15 NoLines
|
|
|
|
Returns the number of lines of the lyrics.
|
|
|
|
*/
|
|
int Lyrics::NoLines() const {
|
|
return linearray.Size();
|
|
}
|
|
|
|
/*
|
|
4.2.16 GetLine
|
|
|
|
Returns a Line indexed by ~i~.
|
|
|
|
*/
|
|
Line Lyrics::GetLine( int i ) const {
|
|
Line l;
|
|
linearray.Get( i, &l );
|
|
return l;
|
|
}
|
|
|
|
|
|
/*
|
|
4.3 List Representation
|
|
|
|
The list representation of a ~lyrics~ are
|
|
|
|
---- ( int string int string ... int string )
|
|
----
|
|
|
|
4.4 ~Out~-Function
|
|
|
|
*/
|
|
ListExpr OutLyrics( ListExpr typeInfo, Word value ) {
|
|
Lyrics *lyrics = (Lyrics *)(value.addr);
|
|
|
|
ListExpr result=nl->TheEmptyList();
|
|
|
|
if (!lyrics->IsDefined()) {
|
|
/* the lyrics tag is not defined, so we have to return
|
|
a nested list with the symbol atom "undef"*/
|
|
return (nl->SymbolAtom("undef"));
|
|
}
|
|
|
|
/* The nested list is build up beginning from the end.
|
|
This is done because the append operator does not seem
|
|
to be very reliable. */
|
|
for (int i = lyrics->NoLines() - 1; i >= 0; i--) {
|
|
int sec = lyrics->GetLine(i).seconds;
|
|
string tline = lyrics->GetLine(i).textline;
|
|
|
|
result = nl->Cons(nl-> StringAtom (tline ), result);
|
|
result = nl->Cons(nl-> IntAtom (sec ), result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
4.5 ~In~-Function
|
|
|
|
*/
|
|
Word InLyrics( const ListExpr typeInfo, const ListExpr instance,
|
|
const int errorPos, ListExpr& errorInfo, bool& correct ){
|
|
Lyrics *lyrics = new Lyrics(0);
|
|
|
|
|
|
/* we have to check whether the nested list contains
|
|
valid lyrics or the symbol atom "undef"*/
|
|
if (nl->IsAtom(instance)
|
|
&& nl->AtomType(instance) == SymbolType
|
|
&& nl->SymbolValue(instance) == "undef") {
|
|
correct = true;
|
|
lyrics->SetDefined (false);
|
|
return SetWord (lyrics);
|
|
}
|
|
|
|
|
|
/* The nested list of a lyrics object consists of alternating
|
|
elements for the time stamps and the text. Therefore the number
|
|
of elements must be even. */
|
|
int nolines = nl->ListLength (instance) / 2;
|
|
|
|
if (!(nl->ListLength (instance) % 2 == 0) ) {
|
|
/* The number of elements is odd. Create an incorrect
|
|
lyrics object. */
|
|
correct = false;
|
|
delete lyrics;
|
|
return SetWord( Address(0));
|
|
}
|
|
|
|
/* Now we have to check whether all element pairs in the list
|
|
have the correct type namely IntType resp. StringType. */
|
|
bool typeOk = true;
|
|
ListExpr iter=instance;
|
|
for (int i = 1; i <= nolines ; i++) {
|
|
if ( nl->IsAtom (nl->First(iter))
|
|
&& nl->AtomType (nl->First(iter)) == IntType
|
|
&& nl->IsAtom (nl->Second(iter))
|
|
&& nl->AtomType (nl->Second(iter)) == StringType) {
|
|
// empty.
|
|
}
|
|
else {
|
|
typeOk=false;
|
|
}
|
|
iter = nl->Rest(nl->Rest(iter));
|
|
}
|
|
|
|
/* Now we can copy each element of the nested list into the
|
|
lyrics object. */
|
|
iter = instance;
|
|
if (typeOk ) {
|
|
for (int i = 1; i <= nolines; i++) {
|
|
int timer = nl->IntValue (nl->First (iter));
|
|
string text = nl->StringValue (nl->Second (iter));
|
|
|
|
Line oneline;
|
|
oneline.seconds = timer;
|
|
char *dummy = (char*) text.c_str();
|
|
|
|
strncpy (oneline.textline,dummy,255);
|
|
|
|
lyrics->Append (oneline);
|
|
|
|
iter = nl->Rest(nl->Rest(iter));
|
|
}
|
|
correct = true;
|
|
lyrics->SetDefined(true);
|
|
return SetWord (lyrics);
|
|
}
|
|
else {
|
|
correct = false;
|
|
delete lyrics;
|
|
return SetWord( Address(0));
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
4.6 The ~Property~-function
|
|
|
|
*/
|
|
ListExpr LyricsProperty() {
|
|
return (nl->TwoElemList
|
|
(nl->FiveElemList
|
|
(nl->StringAtom("Signature"),
|
|
nl->StringAtom("Example Type List"),
|
|
nl->StringAtom("List Rep"),
|
|
nl->StringAtom("Example List"),
|
|
nl->StringAtom("Remarks")),
|
|
nl->FiveElemList
|
|
(nl->StringAtom("-> DATA"),
|
|
nl->StringAtom(Lyrics::BasicType()),
|
|
nl->StringAtom("( int string int string ... )"),
|
|
nl->StringAtom("( 7 'first line' 11 'second line' )"),
|
|
nl->StringAtom(""))));
|
|
}
|
|
|
|
/*
|
|
4.7 ~Create~-function
|
|
|
|
*/
|
|
Word CreateLyrics( const ListExpr typeInfo ) {
|
|
return SetWord(new Lyrics(0));
|
|
}
|
|
|
|
/*
|
|
4.8 ~Delete~-function
|
|
|
|
*/
|
|
void DeleteLyrics( const ListExpr typeInfo, Word& w ) {
|
|
Lyrics *lyrics = (Lyrics *)w.addr;
|
|
lyrics->Destroy();
|
|
delete lyrics;
|
|
w.addr = 0;
|
|
}
|
|
|
|
/*
|
|
4.9 ~Close~-function
|
|
|
|
*/
|
|
void CloseLyrics( const ListExpr typeInfo, Word& w ) {
|
|
delete (Lyrics *)w.addr;
|
|
w.addr = 0;
|
|
}
|
|
|
|
/*
|
|
4.10 ~Clone~-function
|
|
|
|
*/
|
|
Word CloneLyrics( const ListExpr typeInfo, const Word& w ) {
|
|
return SetWord( ((Lyrics *)w.addr)->Clone() );
|
|
}
|
|
|
|
/*
|
|
4.11 ~SizeOf~-function
|
|
|
|
*/
|
|
int SizeOfLyrics() {
|
|
return sizeof(Lyrics);
|
|
}
|
|
/*
|
|
4.12 ~Cast~-function
|
|
|
|
*/
|
|
void* CastLyrics( void* addr ) {
|
|
return new (addr) Lyrics;
|
|
}
|
|
|
|
/*
|
|
4.13 Kind Checking Function
|
|
|
|
This function checks whether the type constructor is applied correctly. Since
|
|
type constructor ~lyrics~ does not have arguments, this is trivial.
|
|
|
|
*/
|
|
bool CheckLyrics( ListExpr type, ListExpr& errorInfo ) {
|
|
return (nl->IsEqual( type, Lyrics::BasicType() ));
|
|
}
|
|
|
|
/*
|
|
4.14 Creation of the Type Constructor Instance
|
|
|
|
*/
|
|
TypeConstructor lyrics(
|
|
// name
|
|
Lyrics::BasicType(),
|
|
// property function describing signature
|
|
LyricsProperty,
|
|
// Out function
|
|
OutLyrics,
|
|
// In function
|
|
InLyrics,
|
|
// SaveToList function
|
|
0,
|
|
// RestoreFromList function
|
|
0,
|
|
// object creation
|
|
CreateLyrics,
|
|
// object deletion
|
|
DeleteLyrics,
|
|
// object open
|
|
0,
|
|
// object save
|
|
0,
|
|
// object close
|
|
CloseLyrics,
|
|
// object clone
|
|
CloneLyrics,
|
|
// cast function
|
|
CastLyrics,
|
|
// sizeof function
|
|
SizeOfLyrics,
|
|
// kind checking function
|
|
CheckLyrics );
|
|
|
|
|
|
|
|
|
|
/*
|
|
5 Operators
|
|
|
|
5.1 Operator ~savemp3to~
|
|
|
|
Saves the binary contents of an mp3 into a file.
|
|
|
|
5.1.1 Type mapping function of operator ~savemp3to~
|
|
|
|
Operator ~savemp3to~ accepts a mp3 object and a string representing
|
|
the name of the file, and returns a boolean meaning success or not.
|
|
|
|
---- (mp3 string) -> bool
|
|
----
|
|
|
|
*/
|
|
ListExpr SaveMP3ToTypeMap( ListExpr args ) {
|
|
if(!nl->HasLength(args,2)){
|
|
return listutils::typeError("two args expected");
|
|
}
|
|
if(!MP3::checkType(nl->First(args))){
|
|
return listutils::typeError("first arg must be an mp3");
|
|
}
|
|
if(!CcString::checkType(nl->Second(args))
|
|
&&!FText::checkType(nl->Second(args))){
|
|
return listutils::typeError("second arg must be a string or a text");
|
|
}
|
|
return listutils::basicSymbol<CcBool>();
|
|
}
|
|
|
|
/*
|
|
|
|
5.1.2 Value mapping functions of operator ~savemp3to~
|
|
|
|
*/
|
|
template<class T>
|
|
int SaveMP3ToFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
CcBool* res = (CcBool*) result.addr;
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
T* fileName = (T*) args[1].addr;
|
|
if(!mp3->IsDefined() || !fileName->IsDefined()){
|
|
res->SetDefined(false);
|
|
return 0;
|
|
}
|
|
res->Set(true, mp3->SaveMP3ToFile(fileName->GetValue().c_str()));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.1.3 Specification of operator ~savemp3to~
|
|
|
|
*/
|
|
const string SaveMP3ToSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3 {string,text}) -> bool"
|
|
"</text--->"
|
|
"<text>_ savemp3to _</text--->"
|
|
"<text>Saves the contents of the mp3 object into a "
|
|
"file.</text--->"
|
|
"<text>query song savemp3to \"song.mp3\"</text--->"
|
|
") )";
|
|
|
|
|
|
int savemp3toSelect(ListExpr args){
|
|
return CcString::checkType(nl->Second(args))?0:1;
|
|
}
|
|
|
|
ValueMapping savemp3toVM[]= {
|
|
SaveMP3ToFun<CcString>,
|
|
SaveMP3ToFun<FText>
|
|
};
|
|
|
|
/*
|
|
|
|
5.1.4 Definition of operator ~savemp3to~
|
|
|
|
*/
|
|
Operator savemp3to (
|
|
"savemp3to",
|
|
SaveMP3ToSpec,
|
|
2,
|
|
savemp3toVM,
|
|
savemp3toSelect,
|
|
SaveMP3ToTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
5.2 Operator ~removelyrics~
|
|
|
|
Removes the Lyrics-Tag of an MP3-File.
|
|
|
|
5.2.1 Type mapping function of operator ~removelyrics~
|
|
|
|
---- (mp3) -> mp3
|
|
----
|
|
|
|
*/
|
|
ListExpr RemoveLyricsTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.2.2 Value mapping functions of operator ~removelyrics~
|
|
|
|
*/
|
|
int RemoveLyricsFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
MP3 *newmp3 = mp3->Clone();
|
|
newmp3->RemoveLyrics();
|
|
result.addr = newmp3;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.2.3 Specification of operator ~removelyrics~
|
|
|
|
*/
|
|
const string RemoveLyricsSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> mp3"
|
|
"</text--->"
|
|
"<text>_ removelyrics </text--->"
|
|
"<text>Removes the lyrics of a mp3 object"
|
|
".</text--->"
|
|
"<text>query song removelyrics</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.2.4 Definition of operator ~removelyrics~
|
|
|
|
*/
|
|
Operator removelyrics (
|
|
// name
|
|
"removelyrics",
|
|
// specification
|
|
RemoveLyricsSpec,
|
|
// value mapping
|
|
RemoveLyricsFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
RemoveLyricsTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.3 Operator ~removeid3~
|
|
|
|
Removes the ID3-Tag of a MP3-File.
|
|
|
|
5.3.1 Type mapping function of operator ~removeid3~
|
|
|
|
---- (mp3) -> mp3
|
|
----
|
|
|
|
*/
|
|
ListExpr RemoveID3TypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.3.2 Value mapping functions of operator ~removeid3~
|
|
|
|
*/
|
|
int RemoveID3Fun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
MP3 *newmp3 = mp3->Clone();
|
|
newmp3->RemoveID();
|
|
|
|
result.addr = newmp3;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.3.3 Specification of operator ~removeid3~
|
|
|
|
*/
|
|
const string RemoveID3Spec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> mp3"
|
|
"</text--->"
|
|
"<text>_ removeid3</text--->"
|
|
"<text>Removes the ID3-Tag of a mp3 objects"
|
|
".</text--->"
|
|
"<text>query song removeid3</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.3.4 Definition of operator ~removeid3~
|
|
|
|
*/
|
|
Operator removeid3 (
|
|
// name
|
|
"removeid3",
|
|
// specification
|
|
RemoveID3Spec,
|
|
// value mapping
|
|
RemoveID3Fun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
RemoveID3TypeMap
|
|
);
|
|
|
|
|
|
|
|
/*
|
|
|
|
5.4 Operator ~submp3~
|
|
|
|
Extracts frames from an MP3 file object and generate a new MP3
|
|
file object which contains specific parts of the song.
|
|
The first integer parameter specifies the start frame and the
|
|
second integer parameter specifies the length of the fragment.
|
|
|
|
5.4.1 Type mapping function of operator ~submp3~
|
|
|
|
---- (mp3, int, int) -> mp3
|
|
----
|
|
|
|
*/
|
|
ListExpr SubMP3TypeMap( ListExpr args ) {
|
|
ListExpr arg1,arg2,arg3;
|
|
if (nl->ListLength(args) == 3) {
|
|
arg1 = nl->First(args);
|
|
arg2 = nl->Second(args);
|
|
arg3 = nl->Third(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType()) &&
|
|
nl->IsEqual(arg2, CcInt::BasicType()) &&
|
|
nl->IsEqual(arg3, CcInt::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.4.2 Value mapping functions of operator ~submp3~
|
|
|
|
*/
|
|
int SubMP3Fun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
|
|
int beginframe = ((CcInt*)args[1].addr)->GetIntval();
|
|
int size = ((CcInt*)args[2].addr)->GetIntval();
|
|
|
|
MP3 *newmp3 = ((MP3*)args[0].addr)->SubMP3 (beginframe, size);
|
|
result.addr = newmp3;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.4.3 Specification of operator ~submp3~
|
|
|
|
*/
|
|
const string SubMP3Spec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3 int int) -> mp3"
|
|
"</text--->"
|
|
"<text> _ _ _ submp3</text--->"
|
|
"<text>Extracts frames from mp3 objects and"
|
|
" saves them into a new mp3 object."
|
|
" Parameters: beginframe and size of frames" "</text--->"
|
|
"<text>query song 12 100 submp3</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.4.4 Definition of operator ~submp3~
|
|
|
|
*/
|
|
Operator submp3 (
|
|
// name
|
|
"submp3",
|
|
// specification
|
|
SubMP3Spec,
|
|
// value mapping
|
|
SubMP3Fun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
SubMP3TypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.5 Operator ~concatmp3~
|
|
|
|
Returns the bitrate of an MP3-File.
|
|
|
|
5.5.1 Type mapping function of operator ~concatmp3~
|
|
|
|
---- (mp3, mp3) -> mp3
|
|
----
|
|
|
|
*/
|
|
ListExpr ConcatMP3TypeMap(ListExpr args) {
|
|
ListExpr arg1,arg2;
|
|
if (nl->ListLength(args) == 2) {
|
|
arg1 = nl->First(args);
|
|
arg2 = nl->Second(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType()) &&
|
|
nl->IsEqual(arg2, MP3::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.5.2 Value mapping functions of operator ~concatmp3~
|
|
|
|
*/
|
|
int ConcatMP3Fun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
|
|
MP3 *firstmp3 = (MP3*)args[0].addr;
|
|
MP3 *secondmp3 = (MP3*)args[1].addr;
|
|
|
|
MP3 *newmp3 = firstmp3->Concat (secondmp3);
|
|
result.addr = newmp3;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.5.3 Specification of operator ~concatmp3~
|
|
|
|
*/
|
|
const string ConcatMP3Spec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3 , mp3) -> mp3"
|
|
"</text--->"
|
|
"<text> _ _ concatmp3</text--->"
|
|
"<text>Concats two mp3 objects"
|
|
".</text--->"
|
|
"<text>query song1 song2 concatmp3</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.5.4 Definition of operator ~concatmp3~
|
|
|
|
*/
|
|
Operator concatmp3 (
|
|
// name
|
|
"concatmp3",
|
|
// Specification
|
|
ConcatMP3Spec,
|
|
// value mapping
|
|
ConcatMP3Fun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
ConcatMP3TypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.6 Operator ~bitrate~
|
|
|
|
Returns the bitrate of an MP3-File.
|
|
|
|
5.6.1 Type mapping function of operator ~bitrate~
|
|
|
|
---- (mp3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr BitrateTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.6.2 Value mapping functions of operator ~bitrate~
|
|
|
|
*/
|
|
int BitrateFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
((CcInt *)result.addr)->Set(mp3->IsDefined(), mp3->GetBitrate());
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.6.3 Specification of operator ~bitrate~
|
|
|
|
*/
|
|
const string BitrateSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> int"
|
|
"</text--->"
|
|
"<text>_ bitrate </text--->"
|
|
"<text>Returns the bitrate of the mp3 object"
|
|
".</text--->"
|
|
"<text>query song bitrate</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.6.4 Definition of operator ~bitrate~
|
|
|
|
*/
|
|
Operator bitrate (
|
|
// name
|
|
"bitrate",
|
|
// specification
|
|
BitrateSpec,
|
|
// value mapping
|
|
BitrateFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
BitrateTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.7 Operator ~version~
|
|
|
|
Returns the version of an MP3-File.
|
|
|
|
5.7.1 Type mapping function of operator ~version~
|
|
|
|
---- (mp3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr VersionTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.7.2 Value mapping functions of operator ~version~
|
|
|
|
*/
|
|
int VersionFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
|
|
((CcInt *)result.addr)->Set(mp3->IsDefined(), mp3->GetMP3Version());
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.7.3 Specification of operator ~version~
|
|
|
|
*/
|
|
const string VersionSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> int"
|
|
"</text--->"
|
|
"<text>_ version</text--->"
|
|
"<text>Returns the version of the mp3 object"
|
|
".</text--->"
|
|
"<text>query song version</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.7.4 Definition of operator ~version~
|
|
|
|
*/
|
|
Operator version (
|
|
// name
|
|
"version",
|
|
// specification
|
|
VersionSpec,
|
|
// value mapping
|
|
VersionFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
VersionTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.8 Operator ~frequency~
|
|
|
|
Returns the frequency of an MP3-File.
|
|
|
|
5.8.1 Type mapping function of operator ~frequency~
|
|
|
|
---- (mp3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr FrequencyTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.8.2 Value mapping functions of operator ~frequency~
|
|
|
|
*/
|
|
int FrequencyFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
|
|
((CcInt *)result.addr)->Set(mp3->IsDefined(), mp3->GetFrequency());
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.8.3 Specification of operator ~frequency~
|
|
|
|
*/
|
|
const string FrequencySpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> int"
|
|
"</text--->"
|
|
"<text>_ frequency</text--->"
|
|
"<text>Returns the frequency of the mp3 object "
|
|
".</text--->"
|
|
"<text>query song frequency</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.8.4 Definition of operator ~frequency~
|
|
|
|
*/
|
|
Operator frequency (
|
|
// name
|
|
"frequency",
|
|
// specification
|
|
FrequencySpec,
|
|
// value mapping
|
|
FrequencyFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
FrequencyTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.9 Operator ~framecount~
|
|
|
|
Returns the number of frames of an MP3-File.
|
|
|
|
5.9.1 Type mapping function of operator ~framecount~
|
|
|
|
---- (mp3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr FrameCountTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.9.2 Value mapping functions of operator ~framecount~
|
|
|
|
*/
|
|
int FrameCountFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
|
|
((CcInt *)result.addr)->Set(mp3->IsDefined(), mp3->GetFrameCount());
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.9.3 Specification of operator ~framecount~
|
|
|
|
*/
|
|
const string FrameCountSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> int"
|
|
"</text--->"
|
|
"<text>_ framecount</text--->"
|
|
"<text>Returns the number of frames within the mp3"
|
|
"object.</text--->"
|
|
"<text>query song framecount</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.9.4 Definition of operator ~framecount~
|
|
|
|
*/
|
|
Operator framecount (
|
|
// name
|
|
"framecount",
|
|
// specification
|
|
FrameCountSpec,
|
|
// value mapping
|
|
FrameCountFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
FrameCountTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.10 Operator ~length~
|
|
|
|
Returns the length of an MP3-File (seconds).
|
|
|
|
5.10.1 Type mapping function of operator ~length~
|
|
|
|
---- (mp3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr LengthTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.10.2 Value mapping functions of operator ~length~
|
|
|
|
*/
|
|
int LengthFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
|
|
((CcInt *)result.addr)->Set(mp3->IsDefined(), mp3->GetLength());
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.10.3 Specification of operator ~length~
|
|
|
|
*/
|
|
const string LengthSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> int"
|
|
"</text--->"
|
|
"<text>_ songlength</text--->"
|
|
"<text>Returns the length of a song (seconds) "
|
|
".</text--->"
|
|
"<text>query song songlength</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.10.4 Definition of operator ~length~
|
|
|
|
*/
|
|
Operator lengthsong (
|
|
// name
|
|
"songlength",
|
|
// specification
|
|
LengthSpec,
|
|
// value mapping
|
|
LengthFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
LengthTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.11 Operator ~getid3~
|
|
|
|
Returns the id3-tag of an MP3-File.
|
|
|
|
5.11.1 Type mapping function of operator ~getid3~
|
|
|
|
---- (mp3) -> id3
|
|
----
|
|
|
|
*/
|
|
ListExpr GetID3TypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(ID3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
5.11.2 Value mapping functions of operator ~getid3~
|
|
|
|
*/
|
|
int GetID3Fun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
ID3* id3= new ID3();
|
|
|
|
/* is the mp3 object defined? */
|
|
if (!mp3->IsDefined()){
|
|
id3->SetDefined(false);
|
|
result.addr=id3;
|
|
return 0;
|
|
}
|
|
|
|
char bytes[128];
|
|
char tag [4] = "TAG";
|
|
mp3->GetID3Dump(bytes);
|
|
|
|
if (!(strncmp (bytes,tag,3) == 0)) {
|
|
/* The first three characters were not "TAG".
|
|
This means there was no ID3 tag in the MP3 song.
|
|
An undefined object is created. */
|
|
id3->SetDefined (false);
|
|
result.addr=id3;
|
|
return 0;
|
|
}
|
|
|
|
/* copy the songname into the new id3 object. */
|
|
strncpy (id3->songname,bytes+3,30);
|
|
*(id3->songname + 30) = (byte) 0x00;
|
|
|
|
/* copy the author into the new id3 object. */
|
|
strncpy (id3->author,bytes+33,30);
|
|
*(id3->author + 30) = (byte) 0x00;
|
|
/* copy the album name into the new id3 object. */
|
|
strncpy (id3->album,bytes+63,30);
|
|
*(id3->album + 30) = (byte) 0x00;
|
|
/* copy the year into the new ID3 object. The year is
|
|
not encoded as a number but as a string. Therefore
|
|
we have to use the atoi function. */
|
|
char year_[5];
|
|
strncpy (year_,bytes+93,4);
|
|
*(year_ + 4) = (byte) 0x00;
|
|
id3->year = atoi (year_);
|
|
|
|
if ( bytes[125] == 0) {
|
|
// the zero byte indicates that we have Version 1.1
|
|
id3->version = 1;
|
|
// copy the comment
|
|
strncpy (id3->comment,bytes+97,28);
|
|
*(id3->comment + 28) = (byte) 0x00;
|
|
/* copy the track number into the new id3 object.
|
|
The track number is encoded as word. (two bytes). */
|
|
id3->track = (byte) (*(bytes+126));
|
|
}
|
|
else {
|
|
id3->version = 0;
|
|
// copy the comment
|
|
strncpy (id3->comment,bytes+97,30);
|
|
*(id3->comment + 30) = (byte) 0x00;
|
|
// since there is no track-number information we can set it to 0
|
|
id3->track = 0;
|
|
}
|
|
|
|
/* copy the genre name into the new id3 object. */
|
|
strncpy (id3->genre,id3->GetGenreName((byte) (*(bytes+127)) ),20);
|
|
*(id3->genre + 21) = (byte) 0x00;
|
|
|
|
id3->SetDefined(true);
|
|
|
|
result.addr=id3;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.11.3 Specification of operator ~getid3~
|
|
|
|
*/
|
|
const string GetID3Spec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> id3"
|
|
"</text--->"
|
|
"<text>_ getid3</text--->"
|
|
"<text>Extracts an id3 object from a song "
|
|
".</text--->"
|
|
"<text>query song getid3</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.11.4 Definition of operator ~getid3~
|
|
|
|
*/
|
|
Operator getid3 (
|
|
// name
|
|
"getid3",
|
|
GetID3Spec, //specification
|
|
// value mapping
|
|
GetID3Fun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
GetID3TypeMap
|
|
);
|
|
|
|
|
|
|
|
/*
|
|
|
|
5.12 Operator ~putid3~
|
|
|
|
Adds an ID3-Tag to the mp3 object.
|
|
|
|
5.12.1 Type mapping function of operator ~putid3~
|
|
|
|
---- (mp3, id3) -> mp3
|
|
----
|
|
|
|
*/
|
|
ListExpr PutID3TypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
ListExpr arg2;
|
|
if (nl->ListLength(args) == 2) {
|
|
arg1 = nl->First(args);
|
|
arg2 = nl->Second(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType()) &&
|
|
nl->IsEqual (arg2,ID3::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.12.2 Value mapping functions of operator ~putid3~
|
|
|
|
*/
|
|
int PutID3Fun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
ID3 *id3 = (ID3*)args[1].addr;
|
|
|
|
MP3 *newmp3 = mp3->Clone();
|
|
|
|
if (id3->IsDefined()){
|
|
char idbytes [128];
|
|
id3->MakeID3Dump (idbytes);
|
|
newmp3->PutID(idbytes);
|
|
}
|
|
|
|
result.addr = newmp3;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.12.3 Specification of operator ~putid3~
|
|
|
|
*/
|
|
const string PutID3Spec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3 id3) -> mp3"
|
|
"</text--->"
|
|
"<text>_ _ putid3</text--->"
|
|
"<text>Adds an id3 Tag to the mp3 song"
|
|
".</text--->"
|
|
"<text>query song id3 putid3</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.12.4 Definition of operator ~putid3~
|
|
|
|
*/
|
|
Operator putid3 (
|
|
// name
|
|
"putid3",
|
|
// specification
|
|
PutID3Spec,
|
|
// value mapping
|
|
PutID3Fun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
PutID3TypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.13 Operator ~author~
|
|
|
|
Gets the author of an ID3-Tag.
|
|
|
|
5.13.1 Type mapping function of operator ~author~
|
|
|
|
---- (id3) -> string
|
|
----
|
|
|
|
*/
|
|
ListExpr AuthorTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcString::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.13.2 Value mapping functions of operator ~author~
|
|
|
|
*/
|
|
int AuthorFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined()){
|
|
char newStr[256];
|
|
id3->GetAuthor (newStr);
|
|
((CcString *)result.addr)->Set( true, (STRING_T*) &newStr );
|
|
return 0;
|
|
}
|
|
else {
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) "");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
5.13.3 Specification of operator ~author~
|
|
|
|
*/
|
|
const string AuthorSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(id3) -> string"
|
|
"</text--->"
|
|
"<text>_ author</text--->"
|
|
"<text>Extracts the author`s name from an id3-object"
|
|
".</text--->"
|
|
"<text>query id3 author</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.13.4 Definition of operator ~author~
|
|
|
|
*/
|
|
Operator author (
|
|
// name
|
|
"author",
|
|
// specification
|
|
AuthorSpec,
|
|
// value mapping
|
|
AuthorFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
AuthorTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.14 Operator ~titleof~
|
|
|
|
Gets the title of an ID3-Tag.
|
|
|
|
5.14.1 Type mapping function of operator ~titleof~
|
|
|
|
---- (id3) -> string
|
|
----
|
|
|
|
*/
|
|
ListExpr TitleTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcString::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.14.2 Value mapping functions of operator ~titleof~
|
|
|
|
*/
|
|
int TitleFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined()){
|
|
char newStr[256];
|
|
id3->GetTitle (newStr);
|
|
|
|
((CcString *)result.addr)->Set( true, (STRING_T*) &newStr );
|
|
return 0;
|
|
}
|
|
else {
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) "");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
5.14.3 Specification of operator ~title~
|
|
|
|
*/
|
|
const string TitleSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>id3 -> string"
|
|
"</text--->"
|
|
"<text>_ titleof</text--->"
|
|
"<text>Extracts the title from an id3 object"
|
|
".</text--->"
|
|
"<text>query id3 titleof</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.14.4 Definition of operator ~titleof~
|
|
|
|
*/
|
|
Operator cctitle (
|
|
// name
|
|
"titleof",
|
|
// specification
|
|
TitleSpec,
|
|
// value mapping
|
|
TitleFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
TitleTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.15 Operator ~album~
|
|
|
|
Gets the album name of an ID3-Tag.
|
|
|
|
5.15.1 Type mapping function of operator ~album~
|
|
|
|
---- (id3) -> string
|
|
----
|
|
|
|
*/
|
|
ListExpr AlbumTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcString::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.15.2 Value mapping functions of operator ~album~
|
|
|
|
*/
|
|
int AlbumFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined()){
|
|
char newStr[256];
|
|
id3->GetAlbum (newStr);
|
|
|
|
((CcString *)result.addr)->Set( true, (STRING_T*) &newStr );
|
|
return 0;
|
|
}
|
|
else {
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) "");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
5.15.3 Specification of operator ~album~
|
|
|
|
*/
|
|
const string AlbumSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(id3) -> string"
|
|
"</text--->"
|
|
"<text>_ album</text--->"
|
|
"<text>Extracts the albumname from an id3 object "
|
|
".</text--->"
|
|
"<text>query id3 album</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.15.4 Definition of operator ~album~
|
|
|
|
*/
|
|
Operator album (
|
|
// name
|
|
"album",
|
|
// specification
|
|
AlbumSpec,
|
|
// value mapping
|
|
AlbumFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
AlbumTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.16 Operator ~comment~
|
|
|
|
Gets the comment of an ID3-Tag.
|
|
|
|
5.16.1 Type mapping function of operator ~comment~
|
|
|
|
---- (id3) -> string
|
|
----
|
|
|
|
*/
|
|
ListExpr CommentTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcString::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.16.2 Value mapping functions of operator ~comment~
|
|
|
|
*/
|
|
int CommentFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined()){
|
|
char newStr[256];
|
|
id3->GetComment (newStr);
|
|
|
|
((CcString *)result.addr)->Set( true, (STRING_T*) &newStr );
|
|
return 0;
|
|
}
|
|
else {
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) "");
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
5.16.3 Specification of operator ~comment~
|
|
|
|
*/
|
|
const string CommentSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> bool"
|
|
"</text--->"
|
|
"<text>_ comment</text--->"
|
|
"<text>Extracts the comment of an id3 object "
|
|
".</text--->"
|
|
"<text>query id3 comment</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.16.4 Definition of operator ~comment~
|
|
|
|
*/
|
|
Operator comment (
|
|
// name
|
|
"comment",
|
|
// specification
|
|
CommentSpec,
|
|
// value mapping
|
|
CommentFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
CommentTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.17 Operator ~genre~
|
|
|
|
Gets the genre of an ID3-Tag.
|
|
|
|
5.17.1 Type mapping function of operator ~genre~
|
|
|
|
---- (id3) -> string
|
|
----
|
|
|
|
*/
|
|
ListExpr GenreTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcString::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.17.2 Value mapping functions of operator ~genre~
|
|
|
|
*/
|
|
int GenreFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined()){
|
|
char newStr[256];
|
|
id3->GetGenre (newStr);
|
|
|
|
((CcString *)result.addr)->Set( true, (STRING_T*) &newStr );
|
|
return 0;
|
|
}
|
|
else {
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) "");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
5.17.3 Specification of operator ~genre~
|
|
|
|
*/
|
|
const string GenreSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(id3) -> string"
|
|
"</text--->"
|
|
"<text>_ genre</text--->"
|
|
"<text>Extracts the genre of an id3 object"
|
|
".</text--->"
|
|
"<text>query id3 genre</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.17.4 Definition of operator ~genre~
|
|
|
|
*/
|
|
Operator genre (
|
|
// name
|
|
"genre",
|
|
// specification
|
|
GenreSpec,
|
|
// value mapping
|
|
GenreFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
GenreTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
|
|
5.18 Operator ~track~
|
|
|
|
Gets the track-no of an ID3-Tag.
|
|
|
|
5.18.1 Type mapping function of operator ~track~
|
|
|
|
---- (id3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr TrackTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.18.2 Value mapping functions of operator ~track~
|
|
|
|
*/
|
|
int TrackFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined())
|
|
((CcInt *)result.addr)->Set( true, id3->GetTrack() );
|
|
else
|
|
((CcInt *)result.addr)->Set( false, 0 );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.18.3 Specification of operator ~track~
|
|
|
|
*/
|
|
const string TrackSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(id3) -> int"
|
|
"</text--->"
|
|
"<text>_ track</text--->"
|
|
"<text>Extracts the track-no from an id3-object "
|
|
".</text--->"
|
|
"<text>query id3 track</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.18.4 Definition of operator ~track~
|
|
|
|
*/
|
|
Operator track (
|
|
// name
|
|
"track",
|
|
// specification
|
|
TrackSpec,
|
|
// value mapping
|
|
TrackFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
TrackTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.19 Operator ~songyear~
|
|
|
|
Gets the year of an ID3-Tag.
|
|
|
|
5.19.1 Type mapping function of operator ~songyear~
|
|
|
|
---- (id3) -> int
|
|
----
|
|
|
|
*/
|
|
ListExpr YearTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, ID3::BasicType())) {
|
|
return nl->SymbolAtom(CcInt::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.19.2 Value mapping functions of operator ~songyear~
|
|
|
|
*/
|
|
int YearFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
ID3 *id3 = (ID3*)args[0].addr;
|
|
|
|
if (id3->IsDefined())
|
|
((CcInt *)result.addr)->Set( true, id3->GetYear() );
|
|
else
|
|
((CcInt *)result.addr)->Set( false, 0 );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.19.3 Specification of operator ~songyear~
|
|
|
|
*/
|
|
const string YearSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(id3) -> int"
|
|
"</text--->"
|
|
"<text>_ songyear</text--->"
|
|
"<text>Extracts the year from an id3-object"
|
|
".</text--->"
|
|
"<text>query id3 songyear</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.19.4 Definition of operator ~songyear~
|
|
|
|
*/
|
|
Operator songyear (
|
|
// name
|
|
"songyear",
|
|
// specification
|
|
YearSpec,
|
|
// value mapping
|
|
YearFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
YearTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.20 Operator ~lyricswords~
|
|
|
|
Gets the corresponding text line to the provided point of time.
|
|
|
|
5.20.1 Type mapping function of operator ~lyricswords~
|
|
|
|
---- (lyrics int) -> string
|
|
----
|
|
|
|
*/
|
|
ListExpr WordsTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
ListExpr arg2;
|
|
if (nl->ListLength(args) == 2) {
|
|
arg1 = nl->First(args);
|
|
arg2 = nl->Second(args);
|
|
if (nl->IsEqual(arg1, Lyrics::BasicType()) &&
|
|
nl->IsEqual(arg2, CcInt::BasicType())) {
|
|
return nl->SymbolAtom(CcString::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.20.2 Value mapping functions of operator ~lyricswords~
|
|
|
|
This function returns the text line of the lyrics that corresponds to the
|
|
given point of time.
|
|
|
|
*/
|
|
int WordsFun(Word* args, Word& result, int message, Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
Lyrics *lyrics = (Lyrics*)args[0].addr;
|
|
int secs = ((CcInt*)args[1].addr)->GetIntval();
|
|
|
|
/* First we have to check whether the lyrics are defined*/
|
|
if (!lyrics->IsDefined() ){
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) "" );
|
|
return 0;
|
|
}
|
|
|
|
/* We have to check whether the point of time is in the intervall
|
|
from the beginning of one line to the beginning of the next
|
|
line. */
|
|
bool found=false;
|
|
char newStr[256];
|
|
for (int i = 0; i < lyrics->NoLines()-1; i++) {
|
|
/* time when ith line begins. */
|
|
int beginsecs = lyrics->GetLine (i).seconds;
|
|
/* time when ith line ends. This is equal to the time
|
|
whean the (i+1)th line begins. */
|
|
int endsecs = lyrics->GetLine (i+1).seconds;
|
|
|
|
if ((secs >= beginsecs) && (secs < endsecs) && (!found)) {
|
|
/* we have found the correct interval. So the string
|
|
has to be copied to newStr. */
|
|
found = true;
|
|
strncpy (newStr,lyrics->GetLine (i).textline,255);
|
|
}
|
|
}
|
|
|
|
/* Now we have to check whether the given time stamp is inside the
|
|
last intervall. Because the length of an intervall (specially
|
|
the last interval) is not available we offer the last line even
|
|
if the time stamp is "behind" the song. */
|
|
if ((!found) && (secs >= lyrics->GetLine(lyrics->NoLines()-1).seconds)) {
|
|
found =true;
|
|
strncpy (newStr,lyrics->GetLine (lyrics->NoLines()-1).textline,255);
|
|
}
|
|
|
|
if (found) {
|
|
((CcString *)result.addr)->Set( true, (STRING_T*) &newStr );
|
|
}
|
|
else
|
|
/* This case can occur if the given time stamp is lower than
|
|
the beginning of the text. */
|
|
((CcString *)result.addr)->Set( false, (STRING_T*) &newStr );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.20.3 Specification of operator ~lyricswords~
|
|
|
|
*/
|
|
const string WordsSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(lyrics int) -> string"
|
|
"</text--->"
|
|
"<text>_ _ lyricswords</text--->"
|
|
"<text>Returns the corresponding text line of "
|
|
"a lyrics.</text--->"
|
|
"<text>query songlyrics 10 lyricswords</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.20.4 Definition of operator ~words~
|
|
|
|
*/
|
|
Operator words (
|
|
// name
|
|
"lyricswords",
|
|
// specification
|
|
WordsSpec,
|
|
// value mapping
|
|
WordsFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
WordsTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.21 Operator ~getlyrics~
|
|
|
|
Gets the lyrics of an MP3-Song.
|
|
|
|
5.21.1 Type mapping function of operator ~getlyrics~
|
|
|
|
---- (mp3) -> lyrics
|
|
----
|
|
|
|
*/
|
|
ListExpr GetLyricsTypeMap( ListExpr args ) {
|
|
ListExpr arg1;
|
|
if (nl->ListLength(args) == 1) {
|
|
arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType())) {
|
|
return nl->SymbolAtom(Lyrics::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.21.2 Value mapping functions of operator ~getlyrics~
|
|
|
|
Returns a lyrics object from an MP3 object with lyrics. If the
|
|
given MP3 object has no lyrics an undefined lyrics object is returned.
|
|
|
|
*/
|
|
int GetLyricsFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage(s);
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
|
|
Lyrics* newlyrics = new Lyrics(0);
|
|
|
|
if (!mp3->IsDefined()) {
|
|
newlyrics->SetDefined (false);
|
|
result.addr = newlyrics;
|
|
return 0;
|
|
}
|
|
|
|
if (!mp3->ExistsLyrics()) {
|
|
newlyrics->SetDefined (false);
|
|
result.addr = newlyrics;
|
|
return 0;
|
|
}
|
|
|
|
/* size of the lyrics dump */
|
|
int size;
|
|
|
|
/* buffer for the lyrics */
|
|
char* buffer = mp3->GetLyricsDump (size);
|
|
|
|
/* iterator for scanning the lyrics text lines. */
|
|
char* iter;
|
|
|
|
/* get the lyrics dump from the MP3 object. */
|
|
|
|
/* The proper lyrics text data begin after the "LYR" tag.
|
|
So we have to look for the first occurance of "LYR"
|
|
in the dump. */
|
|
char *lyrptr = strstr(buffer+1,"LYR");
|
|
|
|
/* The text length is encoded in
|
|
a 5-byte-char-array behind the "LYR". */
|
|
char textsize_ [6];
|
|
strncpy (textsize_, lyrptr + 3, 5);
|
|
textsize_[5] = 0;
|
|
int textsize = atoi(textsize_);
|
|
|
|
Line oneline;
|
|
|
|
/* mulitiline is a pointer to the fist multiline item
|
|
(multiline = [time stamp] + text)
|
|
Example: [00:02]first text line */
|
|
char *multiline = lyrptr + 8;
|
|
iter = multiline;
|
|
|
|
/* Append a [ to make the handling easier. */
|
|
*(multiline + textsize) = *"[";
|
|
|
|
/* scan through all multilines and build up the lyrics object. */
|
|
while ((!(iter == 0)) && (iter < (multiline + textsize))) {
|
|
/* find the next [. This is the beginning of the multiline or
|
|
the end of the multiline text. */
|
|
iter = strstr (iter, "[" );
|
|
|
|
if ((!(iter == 0)) && (iter < (multiline + textsize)) ) {
|
|
/* A multiline was found. */
|
|
|
|
/* digitbuffer is used to convert a string representation
|
|
of a number to int.
|
|
Read the minute information. */
|
|
char digitbuffer[3];
|
|
strncpy (digitbuffer,iter + 1, 2);
|
|
digitbuffer[2] = 0;
|
|
int min = atoi(digitbuffer);
|
|
|
|
/* Read the second information */
|
|
strncpy(digitbuffer, iter + 4, 2);
|
|
digitbuffer[2]=0;
|
|
int sec = atoi(digitbuffer);
|
|
|
|
/* calculate the time stamp for the current line. */
|
|
oneline.seconds = min * 60 + sec;
|
|
|
|
/* extract the text line excl. time information and
|
|
store it into oneline. */
|
|
char * endofchars = strstr(iter + 7, "[");
|
|
int linelength = endofchars-(iter + 7);
|
|
strncpy (oneline.textline, iter + 7, linelength);
|
|
*(oneline.textline + linelength) = 0;
|
|
|
|
/* Append oneline to the new lyrics object. */
|
|
newlyrics->Append (oneline);
|
|
|
|
/* overread the time stamp. */
|
|
iter = iter + 7;
|
|
}
|
|
}
|
|
newlyrics->SetDefined (true);
|
|
result.addr = newlyrics;
|
|
delete[] buffer;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.21.3 Specification of operator ~getlyrics~
|
|
|
|
*/
|
|
const string GetLyricsSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3) -> bool"
|
|
"</text--->"
|
|
"<text>_ getlyrics </text--->"
|
|
"<text>Saves the lyrics of an MP3 object into a "
|
|
"lyrics object.</text--->"
|
|
"<text>query song getlyrics</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.21.4 Definition of operator ~getlyrics~
|
|
|
|
*/
|
|
Operator getlyrics (
|
|
// name
|
|
"getlyrics",
|
|
// specification
|
|
GetLyricsSpec,
|
|
// value mapping
|
|
GetLyricsFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
GetLyricsTypeMap
|
|
);
|
|
|
|
/*
|
|
|
|
5.22 Operator ~putlyrics~
|
|
|
|
Removes the ID3-Tag of an MP3-File.
|
|
|
|
5.22.1 Type mapping function of operator ~putlyrics~
|
|
|
|
---- (mp3, lyrics) -> mp3
|
|
----
|
|
|
|
*/
|
|
ListExpr PutLyricsTypeMap(ListExpr args) {
|
|
ListExpr arg1;
|
|
ListExpr arg2;
|
|
if (nl->ListLength(args) == 2) {
|
|
arg1 = nl->First(args);
|
|
arg2 = nl->Second(args);
|
|
if (nl->IsEqual(arg1, MP3::BasicType()) &&
|
|
nl->IsEqual (arg2,Lyrics::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
/*
|
|
|
|
5.22.2 Value mapping functions of operator ~putlyrics~
|
|
|
|
This function creates and returns a new MP3 objects that contains
|
|
the same song and same ID3 tag but with given lyrics.
|
|
|
|
*/
|
|
int PutLyricsFun(Word* args, Word& result, int message,
|
|
Word& local, Supplier s) {
|
|
result = qp->ResultStorage( s );
|
|
/* old MP3 */
|
|
MP3 *mp3 = (MP3*)args[0].addr;
|
|
/* new lyrics */
|
|
Lyrics *lyrics = (Lyrics*)args[1].addr;
|
|
/* clone the MP3 song to put the lyrics later in. */
|
|
MP3 *newmp3 = mp3->Clone(); //
|
|
|
|
|
|
/* First we have to check whether lyrics are defined, if not we can
|
|
just return the clone of the old mp3*/
|
|
if (!lyrics->IsDefined()){
|
|
result.addr = newmp3;
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate the length of the lyrics multiline text.
|
|
This length has to be provided at the top of the
|
|
multiline part. */
|
|
int textlength=0;
|
|
for (int i = 0; i <lyrics->NoLines(); i++) {
|
|
// dont forget 7 bytes for the time stamp
|
|
textlength = textlength + strlen (lyrics->GetLine(i).textline) + 7;
|
|
}
|
|
|
|
/* Calculate the length which has to be provided before "LYRICS200".
|
|
29 is the length of a minimalized header. */
|
|
int endlength=textlength + 29;
|
|
|
|
/* this is the real length of the whole Lyrics tag in bytes.
|
|
At the end of the lyrics data "LYRICS200" (9 bytes) has to
|
|
be added. Furthermore above length has to be provided in a
|
|
textual representation (= 6 bytes). */
|
|
int blocklength = endlength + 9 + 6;
|
|
|
|
/* Allocate memory for the dump of the lyrics */
|
|
char* lyricbytes = (char*) malloc (blocklength);
|
|
|
|
|
|
char dummy[255];
|
|
|
|
/* Copy a temporary lyrics header into lyricsbytes. The text length
|
|
is inserted later. */
|
|
memcpy (lyricbytes,"LYRICSBEGININD0000211LYR00000",29);
|
|
|
|
/* write the text length to the end of the lyrics header.
|
|
Now the header is finished. */
|
|
sprintf (dummy, "%d",textlength);
|
|
/* 24 = position of the 00000 */
|
|
/* 5 = sizeof("00000") */
|
|
memcpy (lyricbytes + 24 + 5 - strlen(dummy), dummy, strlen(dummy));
|
|
|
|
/* Now we can write the multilines into lyricsbytes. */
|
|
char* position= lyricbytes + 29;
|
|
for (int i = 0; i < lyrics->NoLines(); i++) {
|
|
/* write the time stamp */
|
|
memcpy (position,"[00:00]", 7); // modified later.
|
|
int timesecs = lyrics->GetLine(i).seconds;
|
|
int min = timesecs / 60;
|
|
int secs = timesecs % 60;
|
|
/* writes the minute information into the time stamp. */
|
|
sprintf(dummy,"%d",min);
|
|
memcpy(position + 1 + 2 - strlen(dummy), dummy, strlen(dummy));
|
|
/* writes the second information into the time stamp. */
|
|
sprintf(dummy,"%d",secs);
|
|
memcpy(position + 4 + 2 - strlen (dummy), dummy, strlen (dummy));
|
|
position = position + 7;
|
|
/* Add the text line. */
|
|
char* textlinepointer = lyrics->GetLine(i).textline;
|
|
memcpy (position, textlinepointer, strlen (textlinepointer));
|
|
position = position + strlen (textlinepointer);
|
|
//printf ("%d : %d !\n",min,secs);
|
|
|
|
}
|
|
/* Add the length of the whole lyrics (multilines + header but
|
|
without "LYRICS200" at the end) in bytes. */
|
|
memcpy (position, "000000",6);
|
|
sprintf (dummy, "%d", endlength);
|
|
memcpy (position + 6 - strlen (dummy), dummy, strlen(dummy));
|
|
position = position + 6;
|
|
memcpy (position, "LYRICS200",9);
|
|
|
|
/* insert (replace) the lyrics */
|
|
newmp3->PutLyrics(lyricbytes, blocklength);
|
|
|
|
free (lyricbytes);
|
|
|
|
result.addr = newmp3;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
5.22.3 Specification of operator ~putlyrics~
|
|
|
|
*/
|
|
const string PutLyricsSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text>(mp3 lyrics) -> bool"
|
|
"</text--->"
|
|
"<text>_ _ putlyrics </text--->"
|
|
"<text>put the lyrics into an MP3 "
|
|
"file.</text--->"
|
|
"<text>query song lyrics putlyrics</text--->"
|
|
") )";
|
|
|
|
/*
|
|
|
|
5.22.4 Definition of operator ~putlyrics~
|
|
|
|
*/
|
|
Operator putlyrics (
|
|
// name
|
|
"putlyrics",
|
|
// specification
|
|
PutLyricsSpec,
|
|
// value mapping
|
|
PutLyricsFun,
|
|
// trivial selection function
|
|
Operator::SimpleSelect,
|
|
// type mapping
|
|
PutLyricsTypeMap
|
|
);
|
|
|
|
|
|
/*
|
|
5.23 Operator ~loadmp3from~
|
|
|
|
The operator tries to restore a mp3 from a file, whose name is passed as a text
|
|
or string argument. If the import fails, the result object is undefined.
|
|
|
|
*/
|
|
|
|
ListExpr LoadMP3fromTypeMap( ListExpr args ) {
|
|
if ( nl->ListLength(args) == 1 ) {
|
|
ListExpr arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, FText::BasicType()) ||
|
|
nl->IsEqual(arg1, CcString::BasicType())) {
|
|
return nl->SymbolAtom(MP3::BasicType());
|
|
}
|
|
}
|
|
return nl->SymbolAtom(Symbol::TYPEERROR());
|
|
}
|
|
|
|
int LoadMP3fromSelect( ListExpr args ) {
|
|
ListExpr arg1 = nl->First(args);
|
|
if (nl->IsEqual(arg1, FText::BasicType()) ) { return 0; }
|
|
if (nl->IsEqual(arg1, CcString::BasicType()) ) { return 1; }
|
|
return -1;
|
|
}
|
|
|
|
template<class StringType>
|
|
int LoadMP3fromFun(Word* args, Word& result,
|
|
int message, Word& local, Supplier s){
|
|
result = qp->ResultStorage( s );
|
|
MP3 *res = static_cast<MP3*>(result.addr);
|
|
StringType *filename = static_cast<StringType*>(args[0].addr);
|
|
|
|
if( filename->IsDefined() ){
|
|
res->LoadMP3FromFile( filename->GetValue() );
|
|
} else {
|
|
res->SetDefined( false );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ValueMapping LoadMP3fromValueMap[] = {
|
|
LoadMP3fromFun<FText>,
|
|
LoadMP3fromFun<CcString>
|
|
};
|
|
|
|
static const string LoadMP3fromSpec =
|
|
"( ( \"Signature\" \"Syntax\" \"Meaning\" "
|
|
"\"Example\" ) "
|
|
"( <text> {text|string} -> mp3"
|
|
"</text--->"
|
|
"<text>loadmp3( filename ) </text--->"
|
|
"<text>Creates a mp3 instance from a file.</text--->"
|
|
"<text>query loadmp3('test.mp3')</text--->"
|
|
") )";
|
|
|
|
static Operator loadmp3from("loadmp3from",
|
|
LoadMP3fromSpec,
|
|
2,
|
|
LoadMP3fromValueMap,
|
|
LoadMP3fromSelect,
|
|
LoadMP3fromTypeMap);
|
|
|
|
/*
|
|
|
|
6 Creating the Algebra
|
|
|
|
*/
|
|
|
|
class MP3Algebra : public Algebra {
|
|
public:
|
|
MP3Algebra() : Algebra() {
|
|
AddTypeConstructor( &mp3 );
|
|
AddTypeConstructor( &id3 );
|
|
AddTypeConstructor( &lyrics);
|
|
|
|
//----------
|
|
AddTypeConstructor( &FVectorTC);
|
|
FVectorTC.AssociateKind( Kind::DATA() );
|
|
//----------
|
|
mp3.AssociateKind(Kind::DATA());
|
|
mp3.AssociateKind(Kind::FILE());
|
|
id3.AssociateKind(Kind::DATA());
|
|
id3.AssociateKind(Kind::FILE());
|
|
lyrics.AssociateKind(Kind::DATA());
|
|
lyrics.AssociateKind(Kind::FILE());
|
|
|
|
//--------------
|
|
AddOperator(&getfvectorsOP);
|
|
//--------------
|
|
AddOperator(&savemp3to);
|
|
AddOperator(&loadmp3from);
|
|
AddOperator(&version);
|
|
AddOperator(&frequency);
|
|
AddOperator(&framecount);
|
|
AddOperator(&lengthsong);
|
|
AddOperator(&bitrate);
|
|
AddOperator(&concatmp3);
|
|
AddOperator(&submp3);
|
|
AddOperator(&putid3);
|
|
AddOperator(&removeid3);
|
|
AddOperator(&getid3);
|
|
AddOperator(&author);
|
|
AddOperator(&cctitle);
|
|
AddOperator(&album);
|
|
AddOperator(&comment);
|
|
AddOperator(&track);
|
|
AddOperator(&songyear);
|
|
AddOperator(&genre);
|
|
AddOperator(&words);
|
|
AddOperator(&getlyrics);
|
|
AddOperator(&removelyrics);
|
|
AddOperator(&putlyrics);
|
|
}
|
|
~MP3Algebra() {};
|
|
};
|
|
|
|
} // end namespace mp3
|
|
|
|
/*
|
|
7 Initialization
|
|
|
|
Each algebra module needs an initialization function. The algebra manager
|
|
has a reference to this function if this algebra is included in the list
|
|
of required algebras, thus forcing the linker to include this module.
|
|
|
|
The algebra manager invokes this function to get a reference to the instance
|
|
of the algebra class and to provide references to the global nested list
|
|
container (used to store constructor, type, operator and object information)
|
|
and to the query processor.
|
|
|
|
The function has a C interface to make it possible to load the algebra
|
|
dynamically at runtime.
|
|
|
|
*/
|
|
|
|
extern "C"
|
|
Algebra*
|
|
InitializeMP3Algebra( NestedList* nlRef, QueryProcessor* qpRef ) {
|
|
nl = nlRef;
|
|
qp = qpRef;
|
|
return (new mp3::MP3Algebra);
|
|
}
|
|
|
|
|