Browse Source

Added some tempo metadata extractions methods for CoreAudioFormat and AiffAudioFormat

tags/2021-05-28
jules 12 years ago
parent
commit
1981404103
4 changed files with 350 additions and 2 deletions
  1. +82
    -0
      modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp
  2. +18
    -0
      modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h
  3. +240
    -0
      modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp
  4. +10
    -2
      modules/juce_audio_formats/codecs/juce_CoreAudioFormat.h

+ 82
- 0
modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp View File

@@ -27,6 +27,16 @@
static const char* const aiffFormatName = "AIFF file";
static const char* const aiffExtensions[] = { ".aiff", ".aif", 0 };
//==============================================================================
const char* const AiffAudioFormat::appleOneShot = "apple one shot";
const char* const AiffAudioFormat::appleRootSet = "apple root set";
const char* const AiffAudioFormat::appleRootNote = "apple root note";
const char* const AiffAudioFormat::appleBeats = "apple beats";
const char* const AiffAudioFormat::appleDenominator = "apple denominator";
const char* const AiffAudioFormat::appleNumerator = "apple numerator";
const char* const AiffAudioFormat::appleTag = "apple tag";
const char* const AiffAudioFormat::appleKey = "apple key";
//==============================================================================
namespace AiffFileHelpers
{
@@ -113,6 +123,74 @@ namespace AiffFileHelpers
} JUCE_PACKED;
//==============================================================================
struct BASCChunk
{
enum Key
{
minor = 1,
major = 2,
neither = 3,
both = 4
};
BASCChunk (InputStream& input)
{
zerostruct (*this);
flags = input.readIntBigEndian();
numBeats = input.readIntBigEndian();
rootNote = input.readShortBigEndian();
key = input.readShortBigEndian();
timeSigNum = input.readShortBigEndian();
timeSigDen = input.readShortBigEndian();
oneShot = input.readShortBigEndian();
input.read (unknown, sizeof (unknown));
}
void addToMetadata (StringPairArray& metadata) const
{
const bool rootNoteSet = rootNote != 0;
setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2);
setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet);
if (rootNoteSet)
metadata.set (AiffAudioFormat::appleRootNote, String (rootNote));
metadata.set (AiffAudioFormat::appleBeats, String (numBeats));
metadata.set (AiffAudioFormat::appleDenominator, String (timeSigDen));
metadata.set (AiffAudioFormat::appleNumerator, String (timeSigNum));
const char* keyString = nullptr;
switch (key)
{
case minor: keyString = "major"; break;
case major: keyString = "major"; break;
case neither: keyString = "neither"; break;
case both: keyString = "both"; break;
}
if (keyString != nullptr)
metadata.set (AiffAudioFormat::appleKey, keyString);
}
void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const
{
values.set (name, shouldBeSet ? "1" : "0");
}
uint32 flags;
uint32 numBeats;
uint16 rootNote;
uint16 key;
uint16 timeSigNum;
uint16 timeSigDen;
uint16 oneShot;
uint8 unknown[66];
} JUCE_PACKED;
#if JUCE_MSVC
#pragma pack (pop)
#endif
@@ -392,6 +470,10 @@ public:
input->read (inst, (int) length);
inst->copyTo (metadataValues);
}
else if (type == chunkName ("basc"))
{
AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues);
}
else if ((hasGotVer && hasGotData && hasGotType)
|| chunkEnd < input->getPosition()
|| input->isExhausted())


+ 18
- 0
modules/juce_audio_formats/codecs/juce_AiffAudioFormat.h View File

@@ -39,6 +39,24 @@ public:
/** Destructor. */
~AiffAudioFormat();
//==============================================================================
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleOneShot;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleRootSet;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleRootNote;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleBeats;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleDenominator;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleNumerator;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleTag;
/** Metadata property name used when reading a aiff file with a basc chunk. */
static const char* const appleKey;
//==============================================================================
Array<int> getPossibleSampleRates();
Array<int> getPossibleBitDepths();


+ 240
- 0
modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp View File

@@ -50,6 +50,243 @@ namespace
}
}
//==============================================================================
const char* const CoreAudioFormat::midiDataBase64 = "midiDataBase64";
const char* const CoreAudioFormat::tempo = "tempo";
const char* const CoreAudioFormat::timeSig = "time signature";
//==============================================================================
struct CoreAudioFormatMetatdata
{
static uint32 chunkName (const char* const name) noexcept { return ByteOrder::bigEndianInt (name); }
//==============================================================================
struct FileHeader
{
FileHeader (InputStream& input)
{
fileType = input.readIntBigEndian();
fileVersion = input.readShortBigEndian();
fileFlags = input.readShortBigEndian();
}
uint32 fileType;
uint16 fileVersion;
uint16 fileFlags;
};
//==============================================================================
struct ChunkHeader
{
ChunkHeader (InputStream& input)
{
chunkType = input.readIntBigEndian();
chunkSize = input.readInt64BigEndian();
}
uint32 chunkType;
int64 chunkSize;
};
//==============================================================================
struct AudioDescriptionChunk
{
AudioDescriptionChunk (InputStream& input)
{
sampleRate = input.readDoubleBigEndian();
formatID = input.readIntBigEndian();
formatFlags = input.readIntBigEndian();
bytesPerPacket = input.readIntBigEndian();
framesPerPacket = input.readIntBigEndian();
channelsPerFrame = input.readIntBigEndian();
bitsPerChannel = input.readIntBigEndian();
}
double sampleRate;
uint32 formatID;
uint32 formatFlags;
uint32 bytesPerPacket;
uint32 framesPerPacket;
uint32 channelsPerFrame;
uint32 bitsPerChannel;
};
//==============================================================================
struct UserDefinedChunk
{
UserDefinedChunk (InputStream& input, int64 size)
{
// a user defined chunk contains 16 bytes of a UUID first
uuid[1] = input.readInt64BigEndian();
uuid[0] = input.readInt64BigEndian();
input.skipNextBytes (size - 16);
}
int64 uuid[2];
};
//==============================================================================
static StringPairArray parseMidiChunk (InputStream& input, int64 size)
{
const int64 originalPosition = input.getPosition();
MemoryBlock midiBlock;
input.readIntoMemoryBlock (midiBlock, size);
MemoryInputStream midiInputStream (midiBlock, false);
StringPairArray midiMetadata;
MidiFile midiFile;
if (midiFile.readFrom (midiInputStream))
{
midiMetadata.set (CoreAudioFormat::midiDataBase64, midiBlock.toBase64Encoding());
findTempoEvents (midiFile, midiMetadata);
findTimeSigEvents (midiFile, midiMetadata);
}
input.setPosition (originalPosition + size);
return midiMetadata;
}
static void findTempoEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
{
MidiMessageSequence tempoEvents;
midiFile.findAllTempoEvents (tempoEvents);
const int numTempoEvents = tempoEvents.getNumEvents();
MemoryOutputStream tempoSequence;
for (int i = 0; i < numTempoEvents; ++i)
{
const double tempo = getTempoFromTempoMetaEvent (tempoEvents.getEventPointer (i));
if (tempo > 0.0)
{
if (i == 0)
midiMetadata.set (CoreAudioFormat::tempo, String (tempo));
if (numTempoEvents > 1)
tempoSequence << String (tempo) << ',' << tempoEvents.getEventTime (i) << ';';
}
}
if (tempoSequence.getDataSize() > 0)
midiMetadata.set ("tempo sequence", tempoSequence.toString());
}
static double getTempoFromTempoMetaEvent (MidiMessageSequence::MidiEventHolder* holder)
{
if (holder != nullptr)
{
const MidiMessage& midiMessage = holder->message;
if (midiMessage.isTempoMetaEvent())
{
const double tempoSecondsPerQuarterNote = midiMessage.getTempoSecondsPerQuarterNote();
if (tempoSecondsPerQuarterNote > 0.0)
return 60.0 / tempoSecondsPerQuarterNote;
}
}
return 0.0;
}
static void findTimeSigEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
{
MidiMessageSequence timeSigEvents;
midiFile.findAllTimeSigEvents (timeSigEvents);
const int numTimeSigEvents = timeSigEvents.getNumEvents();
MemoryOutputStream timeSigSequence;
for (int i = 0; i < numTimeSigEvents; ++i)
{
int numerator, denominator;
timeSigEvents.getEventPointer(i)->message.getTimeSignatureInfo (numerator, denominator);
String timeSigString;
timeSigString << numerator << '/' << denominator;
if (i == 0)
midiMetadata.set (CoreAudioFormat::timeSig, timeSigString);
if (numTimeSigEvents > 1)
timeSigSequence << timeSigString << ',' << timeSigEvents.getEventTime (i) << ';';
}
if (timeSigSequence.getDataSize() > 0)
midiMetadata.set ("time signature sequence", timeSigSequence.toString());
}
//==============================================================================
static StringPairArray parseInformationChunk (InputStream& input)
{
StringPairArray infoStrings;
const uint32 numEntries = (uint32) input.readIntBigEndian();
for (uint32 i = 0; i < numEntries; ++i)
infoStrings.set (input.readString(), input.readString());
return infoStrings;
}
//==============================================================================
static bool read (InputStream& input, StringPairArray& metadataValues)
{
const int64 originalPos = input.getPosition();
const FileHeader cafFileHeader (input);
const bool isCafFile = cafFileHeader.fileType == chunkName ("caff");
if (isCafFile)
{
while (! input.isExhausted())
{
const ChunkHeader chunkHeader (input);
if (chunkHeader.chunkType == chunkName ("desc"))
{
AudioDescriptionChunk audioDescriptionChunk (input);
}
else if (chunkHeader.chunkType == chunkName ("uuid"))
{
UserDefinedChunk userDefinedChunk (input, chunkHeader.chunkSize);
}
else if (chunkHeader.chunkType == chunkName ("data"))
{
// -1 signifies an unknown data size so the data has to be at the
// end of the file so we must have finished the header
if (chunkHeader.chunkSize == -1)
break;
input.skipNextBytes (chunkHeader.chunkSize);
}
else if (chunkHeader.chunkType == chunkName ("midi"))
{
metadataValues.addArray (parseMidiChunk (input, chunkHeader.chunkSize));
}
else if (chunkHeader.chunkType == chunkName ("info"))
{
metadataValues.addArray (parseInformationChunk (input));
}
else
{
// we aren't decoding this chunk yet so just skip over it
input.skipNextBytes (chunkHeader.chunkSize);
}
}
}
input.setPosition (originalPos);
return isCafFile;
}
};
//==============================================================================
class CoreAudioReader : public AudioFormatReader
{
@@ -61,6 +298,9 @@ public:
usesFloatingPointData = true;
bitsPerSample = 32;
if (input != nullptr)
CoreAudioFormatMetatdata::read (*input, metadataValues);
OSStatus status = AudioFileOpenWithCallbacks (this,
&readCallback,
nullptr, // write needs to be null to avoid permisisions errors


+ 10
- 2
modules/juce_audio_formats/codecs/juce_CoreAudioFormat.h View File

@@ -44,6 +44,14 @@ public:
/** Destructor. */
~CoreAudioFormat();
//==============================================================================
/** Metadata property name used when reading a caf file with a MIDI chunk. */
static const char* const midiDataBase64;
/** Metadata property name used when reading a caf file with tempo information. */
static const char* const tempo;
/** Metadata property name used when reading a caf file time signature information. */
static const char* const timeSig;
//==============================================================================
Array<int> getPossibleSampleRates();
Array<int> getPossibleBitDepths();
@@ -51,10 +59,10 @@ public:
bool canDoMono();
//==============================================================================
AudioFormatReader* createReaderFor (InputStream* sourceStream,
AudioFormatReader* createReaderFor (InputStream*,
bool deleteStreamIfOpeningFails);
AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo,
AudioFormatWriter* createWriterFor (OutputStream*,
double sampleRateToUse,
unsigned int numberOfChannels,
int bitsPerSample,


Loading…
Cancel
Save