/* libakai - C++ cross-platform akai sample disk reader Copyright (C) 2002-2003 Sébastien Métrot Linux port by Christian Schoenebeck 2003-2014 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Akai.h // Info for the akai disk & file format found here: // http://www.abel.co.uk/~maxim/akai/akaiinfo.htm // #ifndef __akai_h__ #define __akai_h__ // for use with autoconf #ifdef HAVE_CONFIG_H #include #endif #if !defined(_CARBON_) && !defined(_WIN32_) # define LINUX 1 #endif #include #include #include #include #include #include #include #if LINUX # include # include # include # include # include # include #endif typedef std::string String; typedef std::streampos streampos; class AkaiVolume; class AkaiPartition; class AkaiDisk; /* current state of the Akai stream */ typedef enum { akai_stream_ready = 0, akai_stream_end_reached = 1, akai_stream_closed = 2 } akai_stream_state_t; /* stream position dependent to these relations */ typedef enum { akai_stream_start = 0, akai_stream_curpos = 1, akai_stream_end = 2 } akai_stream_whence_t; /* We need to cache IO access to reduce IO system calls which else would slow down things tremendously. For that we differ between the following two cache sizes: CD_FRAMESIZE for CDROM access DISK_CLUSTER_SIZE for normal IO access (e.g. from hard disk) Not yet sure if these are the optimum sizes. */ #ifndef CD_FRAMESIZE # define CD_FRAMESIZE 2048 /* frame size for Yellow Book, Form 1 */ #endif #define DISK_CLUSTER_SIZE 61440 /* 60 kB */ typedef std::string String; /** @brief Accessing AKAI image either from file or a drive (i.e. CDROM). * * This class implements a hardware abstraction layer, providing an abstract * streaming API to read from AKAI data images, no matter if the AKAI image is * already available as image file or whether the respective hardware drive * needs to be accessed directly (i.e. CDROM drive, ZIP drive). So the main task * of this class is isolating operating system dependent file/hardware access. */ class DiskImage { public: DiskImage(const char* path); ///< Open an image from a file. DiskImage(int disk); ///< Open an image from a device number (0='a:', 1='b:', etc...). bool WriteImage(const char* path); ///< Extract Akai data track and write it into a regular file. virtual ~DiskImage(); virtual akai_stream_state_t GetState() const; virtual int GetPos() const; virtual int SetPos(int Where, akai_stream_whence_t Whence = akai_stream_start); virtual int Available (uint WordSize = 1); virtual int Read (void* pData, uint WordCount, uint WordSize); ///< Returns number of successfully read words. void ReadInt8(uint8_t* pData, uint WordCount); void ReadInt16(uint16_t* pData, uint WordCount); void ReadInt32(uint32_t* pData, uint WordCount); int ReadInt8(uint8_t* pData); ///< Returns number of successfully read 8 Bit words. int ReadInt16(uint16_t* pData); ///< Returns number of successfully read 16 Bit words. int ReadInt32(uint32_t* pData); ///< Returns number of successfully read 32 Bit words. uint8_t ReadInt8(); uint16_t ReadInt16(); uint32_t ReadInt32(); virtual uint GetError() const { return 0; } /*virtual const nglChar* GetErrorStr(uint err) const { return _T("No Error"); }*/ protected: #ifdef _WIN32_ HANDLE mFile; #elif defined _CARBON_ || LINUX int mFile; #endif bool mRegularFile; int mPos; int mCluster; int mClusterSize; int mSize; /* in bytes */ #if LINUX /* start and end of the data track we chose (if we're reading from CDROM) */ int mStartFrame; int mEndFrame; #endif // LINUX char* mpCache; void OpenStream(const char* path); inline void swapBytes_16(void* Word); inline void swapBytes_32(void* Word); private: void Init(); }; class Resource { public: Resource() { mRefCount = 0; } virtual ~Resource() { //NGL_ASSERT(mRefCount==0); } uint Acquire() { return mRefCount++; } uint Release() { uint res; //NGL_ASSERT(mRefCount!=0); mRefCount--; res = mRefCount; if (!mRefCount) delete this; return res; } private: uint mRefCount; }; class AkaiDirEntry { public: String mName; uint16_t mType; int mSize; uint16_t mStart; int mIndex; }; // AkaiDiskElement: class AkaiDiskElement : public Resource { public: AkaiDiskElement(uint Offset = 0) { mOffset = Offset; } uint GetOffset() { return mOffset; } protected: void SetOffset(uint Offset) { mOffset = Offset; } void AkaiToAscii(char * buffer, int length); int ReadFAT(DiskImage* pDisk, AkaiPartition* pPartition, int block); bool ReadDirEntry(DiskImage* pDisk, AkaiPartition* pPartition, AkaiDirEntry& rEntry, int block, int pos); private: uint mOffset; }; class AkaiSampleLoop { public: // 4 unsigned Loop 1 marker uint32_t mMarker; // 2 unsigned Loop 1 fine length (65536ths) uint16_t mFineLength; // 4 unsigned Loop 1 coarse length (words) uint32_t mCoarseLength; // 2 unsigned Loop 1 time (msec. or 9999=infinite) uint16_t mTime; private: friend class AkaiSample; bool Load(DiskImage* pDisk); }; class AkaiSample : public AkaiDiskElement { public: AkaiDirEntry GetDirEntry(); // Length Format Description // -------------------------------------------------------------- // 1 3 // 1 Not important: 0 for 22050Hz, 1 for 44100Hz // 1 unsigned MIDI root note (C3=60) uint8_t mMidiRootNote; // 12 AKAII Filename String mName; // 1 128 // 1 unsigned Number of active loops uint8_t mActiveLoops; // 1 unsigned char First active loop (0 for none) uint8_t mFirstActiveLoop; // 1 0 // 1 unsigned Loop mode: 0=in release 1=until release // 2=none 3=play to end uint8_t mLoopMode; // 1 signed Cents tune -50...+50 int8_t mTuneCents; // 1 signed Semi tune -50...+50 int8_t mTuneSemitones; // 4 0,8,2,0 // // 4 unsigned Number of sample words uint32_t mNumberOfSamples; // 4 unsigned Start marker uint32_t mStartMarker; // 4 unsigned End marker uint32_t mEndMarker; // // 4 unsigned Loop 1 marker // 2 unsigned Loop 1 fine length (65536ths) // 4 unsigned Loop 1 coarse length (words) // 2 unsigned Loop 1 time (msec. or 9999=infinite) // // 84 [as above] Loops 2 to 8 // AkaiSampleLoop mLoops[8]; // 4 0,0,255,255 // 2 unsigned Sampling frequency uint16_t mSamplingFrequency; // 1 signed char Loop tune offset -50...+50 int8_t mLoopTuneOffset; // 10 0,0,0... int16_t* mpSamples; bool LoadSampleData(); ///< Load sample into RAM void ReleaseSampleData(); ///< release the samples once you used them if you don't want to be bothered to int SetPos(int Where, akai_stream_whence_t Whence = akai_stream_start); ///< Use this method and Read() if you don't want to load the sample into RAM, thus for disk streaming. int Read(void* pBuffer, uint SampleCount); ///< Use this method and SetPos() if you don't want to load the sample into RAM, thus for disk streaming. bool LoadHeader(); private: AkaiSample(DiskImage* pDisk, AkaiVolume* pParent, const AkaiDirEntry& DirEntry); virtual ~AkaiSample(); friend class AkaiVolume; AkaiVolume* mpParent; DiskImage* mpDisk; AkaiDirEntry mDirEntry; bool mHeaderOK; int mPos; int mImageOffset; // actual position in the image where sample starts }; class AkaiKeygroupSample : public AkaiDiskElement { public: // 35-46 sample 1 name 10,10,10... AKAII character set String mName; // 47 low vel 0 0..127 uint8_t mLowLevel; // 48 high vel 127 0..127 uint8_t mHighLevel; // 49 tune cents 0 -128..127 (-50..50 cents) int8_t mTuneCents; // 50 tune semitones 0 -50..50 int8_t mTuneSemitones; // 51 loudness 0 -50..+50 int8_t mLoudness; // 52 filter 0 -50..+50 int8_t mFilter; // 53 pan 0 -50..+50 int8_t mPan; // 54 loop mode 0 0=AS_SAMPLE 1=LOOP_IN_REL // 2=LOOP_UNTIL_REL 3=NO_LOOP // 4=PLAY_TO_END uint8_t mLoopMode; // 55 (internal use) 255 // 56 (internal use) 255 // 57-58 (internal use) 44,1 // private: friend class AkaiKeygroup; bool Load(DiskImage* pDisk); }; class AkaiEnveloppe { public: // 13 env1 attack 0 0..99 uint8_t mAttack; // 14 env1 decay 30 0..99 uint8_t mDecay; // 15 env1 sustain 99 0..99 uint8_t mSustain; // 16 env1 release 45 0..99 uint8_t mRelease; // 17 env1 vel>attack 0 -50..50 int8_t mVelocityToAttack; // 18 env1 vel>release 0 -50..50 int8_t mVelocityToRelease; // 19 env1 offvel>release 0 -50..50 int8_t mOffVelocityToRelease; // 20 env1 key>dec&rel 0 -50..50 int8_t mKeyToDecayAndRelease; private: friend class AkaiKeygroup; bool Load(DiskImage* pDisk); }; class AkaiKeygroup { public: // byte description default range/comments // --------------------------------------------------------------------------- // 1 keygroup ID 2 // 2-3 next keygroup address 44,1 300,450,600,750.. (16-bit) // 4 low key 24 24..127 uint8_t mLowKey; // 5 high key 127 24..127 uint8_t mHighKey; // 6 tune cents 0 -128..127 (-50..50 cents) int8_t mTuneCents; // 7 tune semitones 0 -50..50 int8_t mTuneSemitones; // 8 filter 99 0..99 uint8_t mFilter; // 9 key>filter 12 0..24 semitone/oct uint8_t mKeyToFilter; // 10 vel>filt 0 -50..50 uint8_t mVelocityToFilter; // 11 pres>filt 0 -50..50 uint8_t mPressureToFilter; // 12 env2>filt 0 -50..50 uint8_t mEnveloppe2ToFilter; // 13 env1 attack 0 0..99 // 14 env1 decay 30 0..99 // 15 env1 sustain 99 0..99 // 16 env1 release 45 0..99 // 17 env1 vel>attack 0 -50..50 // 18 env1 vel>release 0 -50..50 // 19 env1 offvel>release 0 -50..50 // 20 env1 key>dec&rel 0 -50..50 // 21 env2 attack 0 0..99 // 22 env2 decay 50 0..99 // 23 env2 sustain 99 0..99 // 24 env2 release 45 0..99 // 25 env2 vel>attack 0 -50..50 // 26 env2 vel>release 0 -50..50 // 27 env2 offvel>release 0 -50..50 // 28 env2 key>dec&rel 0 -50..50 AkaiEnveloppe mEnveloppes[2]; // 29 vel>env2>filter 0 -50..50 int8_t mVelocityToEnveloppe2ToFilter; // 30 env2>pitch 0 -50..50 int8_t mEnveloppe2ToPitch; // 31 vel zone crossfade 1 0=OFF 1=ON bool mVelocityZoneCrossfade; // 32 vel zones used 4 uint mVelocityZoneUsed; // 33 (internal use) 255 // 34 (internal use) 255 // // 35-46 sample 1 name 10,10,10... AKAII character set // 47 low vel 0 0..127 // 48 high vel 127 0..127 // 49 tune cents 0 -128..127 (-50..50 cents) // 50 tune semitones 0 -50..50 // 51 loudness 0 -50..+50 // 52 filter 0 -50..+50 // 53 pan 0 -50..+50 // 54 loop mode 0 0=AS_SAMPLE 1=LOOP_IN_REL // 2=LOOP_UNTIL_REL 3=NO_LOOP // 4=PLAY_TO_END // 55 (internal use) 255 // 56 (internal use) 255 // 57-58 (internal use) 44,1 // // 59-82 [repeat 35-58 for sample 2] // // 83-106 [repeat 35-58 for sample 3] // // 107-130 [repeat 35-58 for sample 4] // AkaiKeygroupSample mSamples[4]; // 131 beat detune 0 -50..50 int8_t mBeatDetune; // 132 hold attack until loop 0 0=OFF 1=ON bool mHoldAttackUntilLoop; // 133-136 sample 1-4 key tracking 0 0=TRACK 1=FIXED bool mSampleKeyTracking[4]; // 137-140 sample 1-4 aux out offset 0 0..7 uint8_t mSampleAuxOutOffset[4]; // 141-148 vel>sample start 0 -9999..9999 (16-bit signed) int16_t mVelocityToSampleStart[4]; // 149 vel>volume offset 0 -50..50 int8_t mVelocityToVolumeOffset[4]; // 150 (not used) private: friend class AkaiProgram; bool Load(DiskImage* pDisk); }; /** @brief AKAI instrument definition * * Represents exactly one sample based instrument on the AKAI media. */ class AkaiProgram : public AkaiDiskElement { public: AkaiDirEntry GetDirEntry(); // Samples: uint ListSamples(std::list& rSamples); AkaiSample* GetSample(uint Index); AkaiSample* GetSample(const String& rName); // byte description default range/comments // --------------------------------------------------------------------------- // 1 program ID 1 // 2-3 first keygroup address 150,0 // 4-15 program name 10,10,10... AKAII character set String mName; // 16 MIDI program number 0 0..127 uint8_t mMidiProgramNumber; // 17 MIDI channel 0 0..15, 255=OMNI uint8_t mMidiChannel; // 18 polyphony 15 1..16 uint8_t mPolyphony; // 19 priority 1 0=LOW 1=NORM 2=HIGH 3=HOLD uint8_t mPriority; // 20 low key 24 24..127 uint8_t mLowKey; // 21 high key 127 24..127 uint8_t mHighKey; // 22 octave shift 0 -2..2 int8_t mOctaveShift; // 23 aux output select 255 0..7, 255=OFF uint8_t mAuxOutputSelect; // 24 mix output level 99 0..99 uint8_t mMixOutputSelect; // 25 mix output pan 0 -50..50 int8_t mMixPan; // 26 volume 80 0..99 uint8_t mVolume; // 27 vel>volume 20 -50..50 int8_t mVelocityToVolume; // 28 key>volume 0 -50..50 int8_t mKeyToVolume; // 29 pres>volume 0 -50..50 int8_t mPressureToVolume; // 30 pan lfo rate 50 0..99 uint8_t mPanLFORate; // 31 pan lfo depth 0 0..99 uint8_t mPanLFODepth; // 32 pan lfo delay 0 0..99 uint8_t mPanLFODelay; // 33 key>pan 0 -50..50 int8_t mKeyToPan; // 34 lfo rate 50 0..99 uint8_t mLFORate; // 35 lfo depth 0 0..99 uint8_t mLFODepth; // 36 lfo delay 0 0..99 uint8_t mLFODelay; // 37 mod>lfo depth 30 0..99 uint8_t mModulationToLFODepth; // 38 pres>lfo depth 0 0..99 uint8_t mPressureToLFODepth; // 39 vel>lfo depth 0 0..99 uint8_t mVelocityToLFODepth; // 40 bend>pitch 2 0..12 semitones uint8_t mBendToPitch; // 41 pres>pitch 0 -12..12 semitones int8_t mPressureToPitch; // 42 keygroup crossfade 0 0=OFF 1=ON bool mKeygroupCrossfade; // 43 number of keygroups 1 1..99 uint8_t mNumberOfKeygroups; // 44 (internal use) 0 program number // 45-56 key temperament C,C#,D... 0 -25..25 cents int8_t mKeyTemperament[11]; // 57 fx output 0 0=OFF 1=ON bool mFXOutput; // 58 mod>pan 0 -50..50 int8_t mModulationToPan; // 59 stereo coherence 0 0=OFF 1=ON bool mStereoCoherence; // 60 lfo desync 1 0=OFF 1=ON bool mLFODesync; // 61 pitch law 0 0=LINEAR uint8_t mPitchLaw; // 62 voice re-assign 0 0=OLDEST 1=QUIETEST uint8_t mVoiceReassign; // 63 softped>volume 10 0..99 uint8_t mSoftpedToVolume; // 64 softped>attack 10 0..99 uint8_t mSoftpedToAttack; // 65 softped>filt 10 0..99 uint8_t mSoftpedToFilter; // 66 tune cents 0 -128..127 (-50..50 cents) int8_t mSoftpedToTuneCents; // 67 tune semitones 0 -50..50 int8_t mSoftpedToTuneSemitones; // 68 key>lfo rate 0 -50..50 int8_t mKeyToLFORate; // 69 key>lfo depth 0 -50..50 int8_t mKeyToLFODepth; // 70 key>lfo delay 0 -50..50 int8_t mKeyToLFODelay; // 71 voice output scale 1 0=-6dB 1=0dB 2=+12dB uint8_t mVoiceOutputScale; // 72 stereo output scale 0 0=0dB 1=+6dB uint8_t mStereoOutputScale; // 73-150 (not used) AkaiKeygroup* mpKeygroups; bool Load(); AkaiVolume* GetParent() { return mpParent; } private: AkaiProgram(DiskImage* pDisk, AkaiVolume* pParent, const AkaiDirEntry& DirEntry); virtual ~AkaiProgram(); friend class AkaiVolume; std::list mpSamples; AkaiVolume* mpParent; DiskImage* mpDisk; AkaiDirEntry mDirEntry; }; /** @brief Subdivision of an AKAI disk partition. * * An AKAI volume is a further subdivision of an AKAI disk partition. * * An AKAI volume actually provides access to the list of instruments (programs) * and samples. Samples referenced by an instrument (program) are always part of * the same volume. */ class AkaiVolume : public AkaiDiskElement { public: AkaiDirEntry GetDirEntry(); // Programs: uint ListPrograms(std::list& rPrograms); AkaiProgram* GetProgram(uint Index); AkaiProgram* GetProgram(const String& rName); // Samples: uint ListSamples(std::list& rSamples); AkaiSample* GetSample(uint Index); AkaiSample* GetSample(const String& rName); AkaiPartition* GetParent() { return mpParent; } bool IsEmpty(); private: AkaiVolume(DiskImage* pDisk, AkaiPartition* pParent, const AkaiDirEntry& DirEntry); virtual ~AkaiVolume(); uint ReadDir(); friend class AkaiPartition; String mName; std::list mpPrograms; std::list mpSamples; DiskImage* mpDisk; AkaiPartition* mpParent; AkaiDirEntry mDirEntry; }; /** @brief Encapsulates one disk partition of an AKAI disk. * * An object of this class represents exactly one disk partition of an AKAI disk * media or of an AKAI disk image file. This is similar to a hard disk partition * on other operating systems, just in AKAI's own custom format. * * Each AKAI disk partition is further subdivided into so called "volumes". */ class AkaiPartition : public AkaiDiskElement { public: // Samples: uint ListVolumes(std::list& rVolumes); AkaiVolume* GetVolume(uint Index); AkaiVolume* GetVolume(const String& rName); AkaiDisk* GetParent() { return mpParent; } bool IsEmpty(); private: AkaiPartition(DiskImage* pDisk, AkaiDisk* pParent); virtual ~AkaiPartition(); friend class AkaiDisk; String mName; std::list mpVolumes; AkaiDisk* mpParent; DiskImage* mpDisk; }; /** @brief Toplevel AKAI image interpreter. * * This class takes an AKAI disk image as constructor argument and provides * access to the individual partitions of that AKAI disk/image. The concept is * similar to hard disc layout for other operating systems, which are also * divided into individual partitions as topmost instance on the mass data * media. */ class AkaiDisk : public AkaiDiskElement { public: AkaiDisk(DiskImage* pDisk); virtual ~AkaiDisk(); // Partitions: uint GetPartitionCount(); AkaiPartition* GetPartition(uint count); private: DiskImage* mpDisk; std::list mpPartitions; }; #define AKAI_FILE_ENTRY_SIZE 24 #define AKAI_DIR_ENTRY_OFFSET 0xca #define AKAI_DIR_ENTRY_SIZE 16 #define AKAI_ROOT_ENTRY_OFFSET 0x0 #define AKAI_PARTITION_END_MARK 0x8000 #define AKAI_BLOCK_SIZE 0x2000 #define AKAI_FAT_OFFSET 0x70a #define AKAI_MAX_FILE_ENTRIES_S1000 125 //should be 128 ?? #define AKAI_MAX_FILE_ENTRIES_S3000 509 // should be 512 ?? #define AKAI_MAX_DIR_ENTRIES 100 #define AKAI_TYPE_DIR_S1000 1 #define AKAI_TYPE_DIR_S3000 3 #define AKAI_PROGRAM_ID 1 #define AKAI_KEYGROUP_ID 2 #define AKAI_SAMPLE_ID 3 #endif // __akai_h__