/***************************************************************************** * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: waveout_palmos.c 623 2006-02-01 13:19:15Z picard $ * * The Core Pocket Media Player * Copyright (c) 2004-2005 Gabor Kovacs * ****************************************************************************/ #include "../common.h" #if defined(TARGET_PALMOS) #define DRVBUFFER 4096 #define BUFFER_MIN 4*DRVBUFFER #define BUFFER_MUSIC 65536*8 #define BUFFER_VIDEO 65536 #define VOLLIMIT (16384-64) #include "pace.h" typedef struct waveout_palm { waveout_base Base; SndStreamRef Stream; sysregs SysRegs; void* PCMCopy; int CopySpeed; bool_t Mute; int Volume; int PreAmp; int Pan; bool_t Play; bool_t Started; fraction Speed; fraction SpeedTime; buffer Buffer; bool_t BufferMode; int VolumeRamp; int BytePerSample; // output bytes per sample int BytePerSec; // output bytes per seconds (before speed adjustment) int SpeedBytePerSec; // speed adjusted BytePerSec int Ratio; //input to output bytepersec (8.8 fixed point) int Skip; int DrvBufferSize; tick_t DrvBufferDelay; tick_t BufferTick; tick_t Tick; int TimeRef; int BenchSpeed; int BenchLeft; bool_t AlwaysClose; bool_t Force16Bits; int Vol; Boolean Native; m68kcallback CallBack; } waveout_palm; #define WAVEOUT(p) ((waveout_palm*)((char*)(p)-OFS(waveout_palm,Base.Timer))) static void ManualSleep(int MSec) { int t0 = GetTimeTick(); int Wait = (GetTimeFreq()*MSec)/1024; if (Wait==0) Wait=1; while (GetTimeTick()-t0Started) { p->Started = 0; SndStreamStop(p->Stream); ManualSleep(50); // try to make sure callback exited if (p->AlwaysClose) { SndStreamDelete(p->Stream); p->Stream = 0; } p->DrvBufferSize = 0; p->DrvBufferDelay = 0; } return ERR_NONE; } static int CreateStream(waveout_palm* p); static void Start(waveout_palm* p,tick_t CurrTime) { if (p->Play && !p->Started && p->Buffer.WritePos != p->Buffer.ReadPos && (CurrTime<0 || p->BufferTick <= CurrTime+SHOWAHEAD) && (p->Stream || CreateStream(p)==ERR_NONE)) { p->Started = 1; SndStreamStart(p->Stream); } } static int UpdateBufferMode(waveout_palm* p) { int Size; if (p->Started) return ERR_NONE; BufferPack(&p->Buffer,0); Size = p->BufferMode ? BUFFER_VIDEO : BUFFER_MUSIC; if (Context()->LowMemory) Size >>= 1; if (Size < BUFFER_MIN) Size = BUFFER_MIN; Size = ALIGN16(Size); DisableOutOfMemory(); for (;Size >= BUFFER_MIN;Size >>= 1) if (BufferAlloc(&p->Buffer,Size,1)) { EnableOutOfMemory(); if (p->Buffer.WritePos >= p->Buffer.Allocated) p->Buffer.WritePos = 0; return ERR_NONE; } EnableOutOfMemory(); ShowOutOfMemory(); return ERR_OUT_OF_MEMORY; } static Err Callback(waveout_palm* p,SndStreamRef Stream,void *Buffer,uint32_t Frames) { // as a general rule we don't call any OS callbacks here, because // we only have one emulstate structure and if both threads // call an m68k syscall this could mean problems... constplanes SrcPlanes; planes DstPlanes; int SrcLength,DstLength; int WritePos,ReadPos,Length,Speed; LoadSysRegs(&p->SysRegs); Length = Frames * p->BytePerSample; DstPlanes[0] = Buffer; if (p->Started) { WritePos = p->Buffer.WritePos; ReadPos = p->Buffer.ReadPos; if (p->DrvBufferSize != Length) { //assuming double buffering p->DrvBufferSize = Length; p->DrvBufferDelay = Scale(2*Length,TICKSPERSEC,p->SpeedBytePerSec); } Speed = p->CopySpeed; if (Speed <= 0) { /* int Middle = p->Buffer.Allocated >> 1; int Left = ReadPos - WritePos; if (Left<0) Left += p->Buffer.Allocated; Speed = p->BenchSpeed; if (Left > Middle && p->BenchLeft < Left) { Speed -= Left - p->BenchLeft; if (Speed < SPEED_ONE/8) Speed = SPEED_ONE/8; } if (Left < Middle && p->BenchLeft > Left) Speed -= Left - p->BenchLeft; p->BenchSpeed = Speed; p->BenchLeft = Left; */ Speed = SPEED_ONE; } while (Length>0) { if (WritePos < ReadPos) SrcLength = p->Buffer.Allocated - ReadPos; else SrcLength = WritePos - ReadPos; SrcPlanes[0] = p->Buffer.Data + ReadPos; DstLength = Length; PCMConvert(p->PCMCopy,DstPlanes,SrcPlanes,&DstLength,&SrcLength,Speed,256); DstPlanes[0] = (uint8_t*)DstPlanes[0] + DstLength; Length -= DstLength; ReadPos += SrcLength; if (ReadPos == p->Buffer.Allocated) ReadPos = 0; if (!DstLength) break; } p->Buffer.ReadPos = ReadPos; } if (Length) memset(DstPlanes[0],(p->Base.Output.Format.Audio.Flags & PCM_UNSIGNED)?0x80:0x00,Length); return errNone; } DLLEXPORT unsigned long WaveOutCallBack(const void *emulStateP, void *userData68KP, Call68KFuncType *call68KFuncP) { UInt32 Param[4]; memcpy(Param,userData68KP,16); return Callback((waveout_palm*)SWAP32(Param[0]),(SndStreamRef)SWAP32(Param[1]), (void*)SWAP32(Param[2]),SWAP32(Param[3])); } static int Process(waveout_palm* p,const packet* Packet,const flowstate* State) { constplanes SrcPlanes; planes DstPlanes; int Length,SrcLength,DstLength,Left,ReadPos,WritePos; ReadPos = p->Buffer.ReadPos; WritePos = p->Buffer.WritePos; if (!Packet) { if (State->DropLevel) ++p->Base.Dropped; return (!p->Started || !p->Speed.Num || WritePos==ReadPos) ? ERR_NONE : ERR_BUFFER_FULL; } DstLength = (Packet->Length * p->Ratio + 255) >> 8; // it may give little larger as needed length if (DstLength > p->Buffer.Allocated - DRVBUFFER*2) { ++p->Base.Dropped; return ERR_NONE; // drop large packets } // avoid filling the buffer full (it would mean buffer is empty) ReadPos -= p->BytePerSample; if (ReadPos < 0) ReadPos += p->Buffer.Allocated; // check if there is enough free space in buffer Left = ReadPos - WritePos; if (Left<0) Left += p->Buffer.Allocated; if (DstLength > Left) { if (!p->Started) Start(p,State->CurrTime); if (p->Speed.Num) return ERR_BUFFER_FULL; // in benchmark mode we need the package to // be processed to measure real performance WritePos = ReadPos - DstLength; WritePos &= ~3; // align to dword (DstLength is just an approx) if (WritePos<0) WritePos += p->Buffer.Allocated; Left = ReadPos - WritePos; if (Left<0) Left += p->Buffer.Allocated; } // process packet SrcPlanes[0] = Packet->Data[0]; SrcPlanes[1] = Packet->Data[1]; Length = Packet->Length; p->Base.Total += Length; if (Packet->RefTime >= 0) { int Diff = p->Buffer.Allocated - Left; // bytes already in the buffer (with 100% speed) p->BufferTick = Packet->RefTime - Scale(Diff,TICKSPERSEC,p->BytePerSec) - p->DrvBufferDelay; if (p->BufferTick < 0) p->BufferTick = 0; if (p->Started) { // adjust timer if it's already running int Time = GetTimeTick(); tick_t Tick = Scale(Time-p->TimeRef,p->SpeedTime.Num,p->SpeedTime.Den); tick_t Old = p->Tick + Tick; // if difference is little then just adjust (because GetTimeTick() is more linear) if (abs(Old - p->BufferTick) < TICKSPERSEC/2) p->BufferTick = Old + ((p->BufferTick - Old) >> 2); p->Tick = p->BufferTick; p->TimeRef = Time; } } if (p->Skip) { SrcLength = min(p->Skip,Length); SrcPlanes[0] = (uint8_t*)SrcPlanes[0] + SrcLength; SrcPlanes[1] = (uint8_t*)SrcPlanes[1] + SrcLength; Length -= SrcLength; p->Skip -= SrcLength; } while (Length>0) { if (ReadPos < WritePos) DstLength = p->Buffer.Allocated - WritePos; else DstLength = ReadPos - WritePos; DstPlanes[0] = p->Buffer.Data + WritePos; SrcLength = Length; PCMConvert(p->Base.PCM,DstPlanes,SrcPlanes,&DstLength,&SrcLength,SPEED_ONE,p->Vol/4); //volume needed only for lifedrive fix if (p->VolumeRamp < RAMPLIMIT) p->VolumeRamp = VolumeRamp(p->VolumeRamp,DstPlanes[0],DstLength,&p->Base.Output.Format.Audio); SrcPlanes[0] = (uint8_t*)SrcPlanes[0] + SrcLength; SrcPlanes[1] = (uint8_t*)SrcPlanes[1] + SrcLength; Length -= SrcLength; WritePos += DstLength; if (WritePos == p->Buffer.Allocated) WritePos = 0; if (!SrcLength) // unaligned input (not supported) break; } p->Buffer.WritePos = WritePos; if (Length>0 && p->Base.Input.Format.Audio.BlockAlign>0) p->Skip = p->Base.Input.Format.Audio.BlockAlign - Length % p->Base.Input.Format.Audio.BlockAlign; if (!p->Started) Start(p,State->CurrTime); return ERR_NONE; } static int Flush(waveout_palm* p) { Stop(p); p->Skip = 0; p->VolumeRamp = 0; p->BufferTick = -1; p->Buffer.ReadPos = p->Buffer.WritePos = 0; p->BenchSpeed = SPEED_ONE; p->BenchLeft = 0; return ERR_NONE; } static int Done(waveout_palm* p) { Flush(p); if (p->Stream) { // Tungsten T1 seems to still use one or two callbacks after stopping SndStreamSetVolume(p->Stream,0); ManualSleep(100); SndStreamDelete(p->Stream); p->Stream = 0; } BufferClear(&p->Buffer); PCMRelease(p->PCMCopy); p->PCMCopy = NULL; PCMRelease(p->Base.PCM); p->Base.PCM = NULL; return ERR_NONE; } static const int32_t VolDb[201] = { //pow(10,(50+N)/49.828) 10,10,11,11,12,12,13,13,14,15, 16,16,17,18,19,20,21,22,23,24, 25,26,27,29,30,32,33,35,36,38, 40,42,44,46,48,50,53,55,58,61, 64,67,70,73,76,80,84,88,92,97, 101,106,111,116,122,128,134,140,147,154, 161,168,176,185,194,203,212,222,233,244, 256,268,280,294,308,322,337,353,370,388, 406,425,445,466,488,512,536,561,588,616, 645,675,707,741,776,812,851,891,933,977, 1024,1072,1123,1176,1232,1290,1351,1415,1482,1552, 1625,1702,1783,1867,1955,2048,2145,2246,2352,2464, 2580,2702,2830,2964,3104,3251,3405,3566,3734,3911, 4096,4290,4493,4705,4928,5161,5405,5661,5928,6209, 6503,6810,7132,7470,7823,8193,8580,8986,9411,9856, 10323,10811,11322,11858,12418,13006,13621,14265,14940,15646, 16386,17161,17973,18823,19713,20646,21622,22645,23716,24838, 26012,27243,28531,29881,31294,32774,34324,35947,37647,39428, 41293,43246,45291,47433,49676,52026,54486,57063,59762,62589, 65549,68649,71896,75296,78857,82587,86493,90584,94868,99355, 104054}; static int UpdateVolume(waveout_palm* p) { if (p->Stream) { if (p->Mute || p->Volume<0) p->Vol = 0; else { int PreAmp = p->PreAmp+100; if (PreAmp<0) PreAmp=0; if (PreAmp>200) PreAmp=200; p->Vol = Scale(p->Volume,VolDb[PreAmp],100); if (p->Vol > VOLLIMIT) p->Vol = VOLLIMIT; } SndStreamSetVolume(p->Stream,p->Vol); } return ERR_NONE; } static int UpdatePan(waveout_palm* p) { if (p->Stream) SndStreamSetPan(p->Stream,(p->Pan*1024)/100); return ERR_NONE; } static NOINLINE int UpdateSpeed(waveout_palm* p) { p->SpeedTime = p->Speed; p->SpeedTime.Num *= TICKSPERSEC; p->SpeedTime.Den *= GetTimeFreq(); if (!p->Speed.Num) p->CopySpeed = 0; //benchmark mode else p->CopySpeed = Scale(SPEED_ONE,p->Speed.Num,p->Speed.Den); p->SpeedBytePerSec = Scale(p->BytePerSec,p->Speed.Den,p->Speed.Num); p->DrvBufferSize = 0; p->DrvBufferDelay = 0; return ERR_NONE; } static int CreateStream(waveout_palm* p) { SndSampleType Type; SndStreamWidth Width; #if defined(_M_IX86) // this mess is all about avoiding simulator error message (no clue why the orginal pointer is invalid) static uint8_t CallbackJmp[] = {0xB8,0,0,0,0,0xFF,0xE0}; SndStreamBufferCallback CallbackPtr = (SndStreamBufferCallback)CallbackJmp; *(SndStreamBufferCallback*)(CallbackJmp+1) = (SndStreamBufferCallback)Callback; if (!p->Native) CallbackPtr = (SndStreamBufferCallback)m68kCallBack(p->CallBack,(NativeFuncType*)T("tcpmp.dll\0WaveOutCallBack")); #else SndStreamBufferCallback CallbackPtr = (SndStreamBufferCallback)Callback; if (!p->Native) CallbackPtr = (SndStreamBufferCallback)m68kCallBack(p->CallBack,WaveOutCallBack); #endif if (p->Base.Output.Format.Audio.Bits==16) { if (p->Base.Output.Format.Audio.Flags & PCM_SWAPEDBYTES) Type = sndInt16Big; else Type = sndInt16Little; } else if (p->Base.Output.Format.Audio.Bits==8) { if (p->Base.Output.Format.Audio.Flags & PCM_UNSIGNED) Type = sndUInt8; else Type = sndInt8; } else return ERR_NOT_SUPPORTED; switch (p->Base.Output.Format.Audio.Channels) { case 1: Width = sndMono; break; case 2: Width = sndStereo; break; default: return ERR_NOT_SUPPORTED; } SaveSysRegs(&p->SysRegs); if (SndStreamCreate(&p->Stream,sndOutput,p->Base.Output.Format.Audio.SampleRate,Type, Width,CallbackPtr,p,DRVBUFFER,p->Native) != errNone) return ERR_DEVICE_ERROR; UpdateVolume(p); UpdatePan(p); return ERR_NONE; } static void Force16Bits(waveout_palm* p) { if (p->Base.Output.Format.Audio.Bits<16) { p->Base.Output.Format.Audio.Flags &= ~PCM_UNSIGNED; p->Base.Output.Format.Audio.Bits = 16; p->Base.Output.Format.Audio.FracBits = 15; } } static int Init(waveout_palm* p) { int Model = QueryPlatform(PLATFORM_MODEL); int InputRate; int Result; if (p->Force16Bits) Force16Bits(p); // NX80v with MCA2 doesn't like 8bit output? if (Model == MODEL_LIFEDRIVE) p->Base.Output.Format.Audio.Flags |= PCM_LIFEDRIVE_FIX; if (Model == MODEL_TUNGSTEN_T || Model == MODEL_TUNGSTEN_T2 || Model == MODEL_TUNGSTEN_T3) { if (Model != MODEL_TUNGSTEN_T3 && p->Base.Output.Format.Audio.SampleRate > 44100) //48000hz unstable on T|T and T2 p->Base.Output.Format.Audio.SampleRate = 44100; // on my T3 (updated ROM) 24000hz crashes almost always if (p->Base.Output.Format.Audio.SampleRate > 22050 && p->Base.Output.Format.Audio.SampleRate < 32000) p->Base.Output.Format.Audio.SampleRate = 32000; if (p->Base.Output.Format.Audio.SampleRate > 16000 && p->Base.Output.Format.Audio.SampleRate < 22050) p->Base.Output.Format.Audio.SampleRate = 22050; if (p->Base.Output.Format.Audio.SampleRate > 11025 && p->Base.Output.Format.Audio.SampleRate < 16000) p->Base.Output.Format.Audio.SampleRate = 16000; } if (Model == MODEL_ZODIAC) { // it seems Zodiac doesn't support 48000Hz if (p->Base.Output.Format.Audio.SampleRate > 44100) p->Base.Output.Format.Audio.SampleRate = 44100; } // in general for digital camera movies: if (p->Base.Output.Format.Audio.SampleRate > 8000 && p->Base.Output.Format.Audio.SampleRate < 11025) { p->Base.Output.Format.Audio.SampleRate = 11025; Force16Bits(p); } if (p->Base.Output.Format.Audio.SampleRate <= 8000) { p->Base.Output.Format.Audio.SampleRate = 8000; Force16Bits(p); } InputRate = (p->Base.Input.Format.Audio.Bits/8)*p->Base.Input.Format.Audio.SampleRate; if (!(p->Base.Input.Format.Audio.Flags & PCM_PLANES)) InputRate *= p->Base.Input.Format.Audio.Channels; if (!InputRate) return ERR_NOT_SUPPORTED; Result = CreateStream(p); if (Result != ERR_NONE) return Result; p->BufferMode = 1; p->TimeRef = GetTimeTick(); p->BytePerSample = (p->Base.Output.Format.Audio.Bits * p->Base.Output.Format.Audio.Channels)/8; p->BytePerSec = p->Base.Output.Format.Audio.SampleRate * p->BytePerSample; p->Ratio = (256 * p->BytePerSec + InputRate - 1) / InputRate; WaveOutPCM(&p->Base); p->PCMCopy = PCMCreate(&p->Base.Output.Format.Audio,&p->Base.Output.Format.Audio,0,0); if (!p->PCMCopy || !p->Base.PCM) return ERR_OUT_OF_MEMORY; if (UpdateBufferMode(p) != ERR_NONE) return ERR_OUT_OF_MEMORY; UpdateSpeed(p); return ERR_NONE; } static int UpdatePlay(waveout_palm* p) { if (p->Play) { p->TimeRef = GetTimeTick(); Start(p,p->Tick); } else { //tick_t Adjust = p->DrvBufferDelay; can be too big jump into the future, which is disturbing at pause int Tick = GetTimeTick(); Stop(p); p->Tick += Scale(Tick-p->TimeRef,p->SpeedTime.Num,p->SpeedTime.Den);// + Adjust; } return ERR_NONE; } static int TimerGet(void* pt, int No, void* Data, int Size) { waveout_palm* p = WAVEOUT(pt); int Result = ERR_INVALID_PARAM; switch (No) { case TIMER_PLAY: GETVALUE(p->Play,bool_t); break; case TIMER_SPEED: GETVALUE(p->Speed,fraction); break; case TIMER_TIME: assert(Size == sizeof(tick_t)); if (p->Speed.Num==0) *(tick_t*)Data = TIME_BENCH; else if (p->Play) *(tick_t*)Data = p->Tick + Scale(GetTimeTick()-p->TimeRef,p->SpeedTime.Num,p->SpeedTime.Den); else *(tick_t*)Data = p->Tick; Result = ERR_NONE; break; } return Result; } static int TimerSet(void* pt, int No, const void* Data, int Size) { waveout_palm* p = WAVEOUT(pt); int Result = ERR_INVALID_PARAM; switch (No) { case TIMER_PLAY: SETVALUECMP(p->Play,bool_t,UpdatePlay(p),EqBool); break; case TIMER_SPEED: SETVALUECMP(p->Speed,fraction,UpdateSpeed(p),EqFrac); break; case TIMER_TIME: assert(Size == sizeof(tick_t)); p->Tick = *(tick_t*)Data; p->TimeRef = GetTimeTick(); Result = ERR_NONE; break; } return Result; } static int Get(waveout_palm* p, int No, void* Data, int Size) { int Result = WaveOutBaseGet(&p->Base,No,Data,Size); switch (No) { case AOUT_VOLUME: GETVALUE(p->Volume,int); break; case AOUT_MUTE: GETVALUE(p->Mute,bool_t); break; case AOUT_PREAMP: GETVALUE(p->PreAmp,int); break; case AOUT_PAN: GETVALUE(p->Pan,int); break; case AOUT_MODE: GETVALUE(p->BufferMode,bool_t); break; } return Result; } static int Set(waveout_palm* p, int No, const void* Data, int Size) { int Result = WaveOutBaseSet(&p->Base,No,Data,Size); switch (No) { case AOUT_VOLUME: SETVALUECMP(p->Volume,int,UpdateVolume(p),EqInt); break; case AOUT_MUTE: SETVALUECMP(p->Mute,bool_t,UpdateVolume(p),EqBool); break; case AOUT_PREAMP: SETVALUECMP(p->PreAmp,int,UpdateVolume(p),EqInt); break; case AOUT_PAN: SETVALUECMP(p->Pan,int,UpdatePan(p),EqInt); break; case AOUT_MODE: SETVALUECMP(p->BufferMode,bool_t,UpdateBufferMode(p),EqBool); break; case FLOW_FLUSH: Result = Flush(p); break; } return Result; } static int Create(waveout_palm* p) { int Model = QueryPlatform(PLATFORM_MODEL); UInt32 CompanyID; UInt32 Version; if (Model!=MODEL_TUNGSTEN_T && (FtrGet(sysFileCSoundMgr, sndFtrIDVersion, &Version)!=errNone || Version<100)) return ERR_NOT_SUPPORTED; p->Base.Init = (nodefunc)Init; p->Base.Done = (nodefunc)Done; p->Base.Process = (packetprocess)Process; p->Base.Node.Get = (nodeget)Get; p->Base.Node.Set = (nodeset)Set; p->Base.Timer.Class = TIMER_CLASS; p->Base.Timer.Enum = TimerEnum; p->Base.Timer.Get = TimerGet; p->Base.Timer.Set = TimerSet; p->Base.Quality = 2; p->Speed.Num = 1; p->Speed.Den = 1; FtrGet(sysFtrCreator, sysFtrNumOEMCompanyID, &CompanyID); FtrGet(sysFtrCreator, sysFtrNumROMVersion, &Version); // I think MCA2 doesn't like more stop and start sequences. Stream has to deleted and recreated p->AlwaysClose = CompanyID == 'sony' && sysGetROMVerMajor(Version)==5 && sysGetROMVerMinor(Version)<2; p->Force16Bits = sysGetROMVerMajor(Version)==5 && sysGetROMVerMinor(Version)<2; p->Native = 1; if (Model==MODEL_QDA700) p->Native = 0; // crashes with ARM callback(?) return ERR_NONE; } static const nodedef WaveOut = { sizeof(waveout_palm)|CF_GLOBAL, WAVEOUT_ID, AOUT_CLASS, PRI_DEFAULT, (nodecreate)Create, }; void WaveOut_Init() { NodeRegisterClass(&WaveOut); } void WaveOut_Done() { NodeUnRegisterClass(WAVEOUT_ID); } #endif