Advertisement
Kitomas

part of fdm_encode.c (wip)

Sep 21st, 2023
932
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 13.67 KB | None | 0 0
  1. typedef struct subchunk_fmt {
  2.   Uint16        format; //1 for pcm, 3 for float (anything else is unsupported)
  3.   Uint16      channels; //number of interleaved channels; L&R for stereo (2)
  4.   Uint32    sampleRate; //number of samples per second, in hertz
  5.   Uint32      byteRate; //the number of bytes per second of audio
  6.   Uint16    blockAlign; //=(channels*bitsPerSample)/8
  7.   Uint16 bitsPerSample;
  8.   //below is only applicable for format -2 (extensible), i think
  9.   Uint16 extensionSize; //=0 or 22
  10.   Uint32   channelMask; //speaker position mask
  11.   union {
  12.     Uint16    format;
  13.     char  GUID_s[16];
  14.     Uint64 GUID_n[2];
  15.   } /*----------*/ sub; //guid, including data format code
  16. } subchunk_fmt;
  17.  
  18.  
  19. #define _CHUNK_HEADER_SIZE (sizeof(Uint32)+sizeof(Uint32))
  20. typedef struct _subchunk {
  21.   Uint32             id;
  22.   Uint32           size;
  23.   union {
  24.     Uint32       waveID; //part of wave header
  25.     subchunk_fmt    fmt;
  26.     Uint32 sampleLength; //# of sample frames; part of "fact" subchunk
  27.     Uint8       data[1]; //only element of "data" subchunk
  28.   };
  29. } _subchunk;
  30.  
  31.  
  32.  
  33.  
  34. typedef struct kit_kmixerPCM {
  35.   kit_kmixerVoiceSpec spec;
  36.   Uint64      dataSize;
  37.   Uint64    numSamples;
  38.   Uint64     loopStart;
  39.   Uint64       loopEnd;
  40.   Uint32      byteRate;
  41.   Uint16 bitsPerSample;
  42.   Uint8      loopCount; //255 (-1) for infinite loop
  43.   Uint8          flags;
  44.   union { //pcm data should be contiguous with the struct itself
  45.     void*  data; //should be equal to (void*)pcm_struct+sizeof(kit_kmixerPCM)
  46.     Uint8*  u_8;
  47.     Sint16* i16;
  48.     Sint32* i32;
  49.     float*  f32;
  50.     struct { Uint8  l; Uint8  r; }* u_8s;
  51.     struct { Sint16 l; Sint16 r; }* i16s;
  52.     struct { Sint32 l; Sint32 r; }* i32s;
  53.     struct { float  l; float  r; }* f32s;
  54.   };
  55. } kit_kmixerPCM;
  56.  
  57.  
  58.  
  59. size_t kit_coreFileSize(const char* filePath){
  60.   size_t fileSize = 0;
  61.   _IF_SDLERR(filePath==NULL,;,"!filePath")
  62.   FILE* file = fopen(filePath,"rb");
  63.   _IF_SDLERR(file==NULL,;,"!fopen")
  64.  
  65.   int result = fseek(file, 0L, SEEK_END);
  66.   _IF_SDLERR(result,fclose(file),"fseek()=%i",result)
  67.  
  68.   size_t _fileSize = ftell(file);
  69.   _IF_SDLERR(!_fileSize,fclose(file),"fileSize=0")
  70.   _IF_SDLERR(_fileSize==-1,fclose(file),"fileSize=-1")
  71.  
  72.   result = fclose(file);
  73.   _IF_SDLERR(result,;,"fclose()=%i",result)
  74.  
  75.   fileSize = _fileSize;
  76.   _error_:
  77.   return fileSize;
  78. }
  79.  
  80.  
  81. #define DEFAULT_RW_CHUNK_SIZE 4096
  82. size_t kit_coreFileReadBin(const char* filePath, void* buffer_p, size_t chunkSize){
  83.   size_t dataSize = 0;
  84.   void* dataStart = NULL;
  85.   size_t fileSize = 0;
  86.   SDL_bool success =SDL_FALSE;
  87.   _IF_SDLERR(buffer_p==NULL,;,"!buffer_p")
  88.  
  89.  
  90.   //get size of file, in bytes
  91.   fileSize = kit_coreFileSize(filePath);
  92.   _IF_GOTO_ERROR(!fileSize,;)
  93.  
  94.  
  95.   //open file handle
  96.   FILE* file = fopen(filePath,"rb");
  97.   _IF_SDLERR(file==NULL,;,"!fopen")
  98.  
  99.  
  100.   //allocate memory for file data
  101.   dataStart = SDL_malloc(fileSize);
  102.   _IF_SDLERR(dataStart==NULL,fclose(file),"!dataStart")
  103.  
  104.  
  105.   //read bytes from file into fileData
  106.   if(!chunkSize) chunkSize = DEFAULT_RW_CHUNK_SIZE;
  107.   size_t bytesRead, totalRead = 0;
  108.   void* dataPtr = dataStart;
  109.   void* dataEnd = dataStart+fileSize;
  110.   while((dataPtr+chunkSize) < dataEnd){
  111.     bytesRead = fread(dataPtr,1,chunkSize,file);
  112.     _IF_SDLERR(bytesRead<chunkSize,fclose(file),"!fread; bytesRead<chunkSize")
  113.     totalRead += bytesRead;
  114.     dataPtr += chunkSize;
  115.   }
  116.  
  117.   size_t remainingBytes = fileSize-totalRead;
  118.   if(remainingBytes) bytesRead = fread(dataPtr,1,remainingBytes,file);
  119.   _IF_SDLERR(bytesRead<remainingBytes,fclose(file),"!fread; bytesRead<remainingBytes")
  120.   //totalRead += bytesRead; (unnecessary)
  121.  
  122.  
  123.   //close file handle
  124.   int result = fclose(file);
  125.   _IF_SDLERR(result,;,"fclose()=%i",result)
  126.  
  127.  
  128.   success = SDL_TRUE;
  129.   _error_:
  130.   if(success){
  131.     *(void**)buffer_p = dataStart;
  132.     dataSize = fileSize;
  133.   } else if(dataStart!=NULL && !success){
  134.     SDL_free(dataStart);
  135.   }
  136.   return dataSize;
  137. }
  138.  
  139.  
  140. #define WAVE_FORMAT_PCM 0x0001
  141. #define WAVE_FORMAT_IEEE_FLOAT 0x0003
  142. #define WAVE_FORMAT_EXTENSIBLE 0xFFFE
  143. enum _wav_ids {
  144.   id_RIFF=0x46464952, //="RIFF"
  145.   id_WAVE=0x45564157, //="WAVE"
  146.   id_fmt =0x20746D66, //="fmt "
  147.   id_fact=0x74636166, //="fact"
  148.   //id_PEAK=0x4B414550, //="PEAK"
  149.   id_data=0x61746164, //="data"
  150. };
  151. kit_kmixerPCM* kit_kmixerWAVLoad(const char* filePath){
  152.   kit_kmixerPCM* pcm = NULL;
  153.   void* fileDataStart = NULL;
  154.   SDL_bool success = SDL_FALSE;
  155.   size_t fileSize = kit_coreFileReadBin(filePath,&fileDataStart,0);
  156.   _IF_SDLERR(!fileSize,;,"!file")
  157.   _IF_SDLERR(fileSize<44,;,"fileSize<44")
  158.   void* fileDataEnd = fileDataStart+fileSize;
  159.  
  160.   void* data = fileDataStart; //test file=3996872B
  161.   _IF_GOTO_ERROR(kit_coreRealloc(&pcm,0,sizeof(kit_kmixerPCM)),;)
  162.   SDL_malloc(sizeof(kit_kmixerPCM));
  163.   _IF_SDLERR(pcm==NULL,;,"!pcm")
  164.  
  165.  
  166.   //verify wave header
  167.   _subchunk* subchunk = data;
  168.   _IF_SDLERR(subchunk->id!=id_RIFF,;,"chunk ID!=\"RIFF\"")
  169.   _IF_SDLERR(subchunk->size!=(fileSize-8),;,"chunkSize!=(fileSize-8)")
  170.   _IF_SDLERR(subchunk->waveID!=id_WAVE,;,"waveID!=\"WAVE\"")
  171.   data+=_CHUNK_HEADER_SIZE;
  172.   data+=sizeof(Uint32);
  173.   _IF_SDLERR(data>=fileDataEnd,;,"buffer overflow") //should be impossible
  174.  
  175.  
  176.   //process subchunks
  177.   SDL_bool has_fmt  = SDL_FALSE;
  178.   SDL_bool has_data = SDL_FALSE;
  179.   SDL_bool has_fact = SDL_FALSE; //optional subchunk
  180.   while(data < fileDataEnd){
  181.     subchunk = data;
  182.     switch(subchunk->id){
  183.     case id_fmt:; //contains most pcm info
  184.        //format
  185.       Uint16 bitsPerSample = subchunk->fmt.bitsPerSample;
  186.       Uint16 format=subchunk->fmt.format;
  187.       switch(format){
  188.       case WAVE_FORMAT_PCM: _format_pcm:
  189.         if(     bitsPerSample== 8) pcm->spec.format = AUDIO_U8 ;
  190.         else if(bitsPerSample==16) pcm->spec.format = AUDIO_S16;
  191.         else if(bitsPerSample==32) pcm->spec.format = AUDIO_S32;
  192.         else _IS_SDLERR(;,"PCM && bitsPerSample!=<8,16,32>")
  193.         break;
  194.       case WAVE_FORMAT_IEEE_FLOAT: _format_float:
  195.         pcm->spec.format = AUDIO_F32;
  196.         _IF_SDLERR(bitsPerSample!=32,;,"float && bitsPerSample!=32")
  197.         break;
  198.       case WAVE_FORMAT_EXTENSIBLE:
  199.         _IF_SDLERR(subchunk->fmt.extensionSize!=22,;,"extensionSize!=22")
  200.         format=subchunk->fmt.sub.format; //instead of subchunk->fmt.format
  201.         if(format == WAVE_FORMAT_PCM) goto _format_pcm;
  202.         if(format == WAVE_FORMAT_IEEE_FLOAT) goto _format_float;
  203.         SDL_FALLTHROUGH; //go to default when format is not recognized
  204.       default: _IS_SDLERR(;,"unknown format 0x%04X",subchunk->fmt.format) }
  205.        //channels
  206.       _IF_SDLERR(!subchunk->fmt.channels,;,"channels=0")
  207.       _IF_SDLERR(subchunk->fmt.channels>2,;,"channels>2")
  208.       pcm->spec.stereo = subchunk->fmt.channels==2;
  209.        //sampleRate
  210.       _IF_SDLERR(subchunk->fmt.sampleRate<1000,;,"sampleRate<1kHz")
  211.       _IF_SDLERR(subchunk->fmt.sampleRate>384000,;,"sampleRate>384kHz")
  212.       pcm->spec.freq = subchunk->fmt.sampleRate;
  213.        //bitsPerSample
  214.       _IF_SDLERR((bitsPerSample%8)!=0,;,"(bitsPerSample%%8)!=0")
  215.       pcm->bitsPerSample = bitsPerSample;
  216.        //byteRate
  217.       Uint16 bytesPerSample = bitsPerSample/8;
  218.       Uint32 correctByteRate = (bytesPerSample*pcm->spec.freq)<<pcm->spec.stereo;
  219.       _IF_SDLERR(subchunk->fmt.byteRate!=correctByteRate,;,"incorrect byteRate")
  220.       pcm->byteRate = subchunk->fmt.byteRate;
  221.       has_fmt = SDL_TRUE; break;
  222.      
  223.     case id_data: //contains sample data (contiguous with pcm struct itself)
  224.       //(not using kit_coreRealloc here, because its memset 0 would be redundant)
  225.       pcm = SDL_realloc(pcm, sizeof(kit_kmixerPCM)+subchunk->size);
  226.       _IF_SDLERR(pcm==NULL,;,"!pcm->data")
  227.       pcm->data = (void*)pcm+sizeof(kit_kmixerPCM);
  228.       kit_coreMemcpy(pcm->data, subchunk->data, subchunk->size);
  229.       pcm->dataSize = subchunk->size;
  230.       has_data = SDL_TRUE; break;
  231.      
  232.     case id_fact: //contains numSamples value
  233.       pcm->numSamples = subchunk->sampleLength;
  234.       has_fact = SDL_TRUE; break;
  235.      
  236.     default:; //other subchunks are ignored
  237.     }
  238.     data += _CHUNK_HEADER_SIZE;
  239.     data += subchunk->size;
  240.   }
  241.  
  242.  
  243.   _IF_SDLERR(!has_fmt,;,"fmt subchunk not found")
  244.   _IF_SDLERR(!has_data,;,"data subchunk not found")
  245.   if(!has_fact){ //if no fact subchunk found, calculate numSamples
  246.     pcm->numSamples = pcm->dataSize;
  247.     pcm->numSamples /= pcm->bitsPerSample/8;
  248.     pcm->numSamples >>= pcm->spec.stereo;
  249.   }
  250.  
  251.  
  252.   success = SDL_TRUE;
  253.   _error_:
  254.   if(fileDataStart != NULL) SDL_free(fileDataStart);
  255.   if(pcm!=NULL && !success){
  256.     SDL_free(pcm);
  257.     pcm = NULL;
  258.   }
  259.   return pcm;
  260. }
  261.  
  262.  
  263.  
  264.  
  265. #define FDM_MAGIC_NUMBER 0x006D6466
  266. typedef struct kit_kmixerFDMHeader {
  267.   union {
  268.     char      s[4]; //="fdm\x00"
  269.     Uint32       n; //=0x006D6466
  270.   } /*------*/ magic;
  271.   Uint16 headerSize; //headerSize includes headerSize itself, and magic
  272.   Uint16       type; //type 0 = static delta, static history
  273.   Uint64   dataSize;
  274.   Uint64 numSamples;
  275.   Uint64  loopStart;
  276.   Uint64    loopEnd;
  277.   float       delta; //% of change, relative to HALF range; 0.0 -> 2.0
  278.   Uint8   loopCount; //255 (-1) for inf. loop
  279.   Uint8   remainder; //=numSamples%8
  280.   Uint8  historyLen; //number of samples in filter; 0 -> 63
  281.   Uint8    channels;
  282. } kit_kmixerFDMHeader;
  283.  
  284.  
  285. /* WIP
  286. static inline Sint32 _getDelta(Sint16 input, Sint32 current, Sint32 deltaMax,
  287.                                Uint64 history, Uint8 historyLen)
  288. {
  289.   if
  290.   history = (history<<1)
  291.   int _historyLen = historyLen;
  292.   int historyBitCount=0;
  293.   while(_historyLen--){
  294.     historyBitCount += history&1;
  295.     history>>=1;
  296.   }
  297.   float fraction = -1 + ;
  298.  
  299.   Sint32 new = input;
  300. }
  301. Uint16 _kit_kmixerFDMEncode(Uint8* output, Sint16* input, kit_kmixerFDMHeader* hdr){
  302.   Uint64 numSamples = hdr->numSamples;
  303.   Uint64 totalError = 0;
  304.   Uint8 historyLen = hdr->historyLen;
  305.   Sint32 deltaMax = hdr->delta*32767;
  306.  
  307.   if(hdr->channels==1){
  308.     Uint64 history;
  309.     Uint8 currentByte;
  310.     Sint32 currentValue, currentDelta;
  311.     for(Uint64 i=0; i<numSamples; ++i){
  312.      
  313.     }
  314.   } else { //assumed stereo
  315.     //struct { Sint16 l; Sint16 r; }* input_s = (void*)input;
  316.     //for(Uint64 i=0; i<numSamples; ++i)
  317.   }
  318.  
  319.   return totalError / numSamples;
  320. }
  321. */
  322. int kit_kmixerFDMWrite(const char* fileName, kit_kmixerPCM* pcm,
  323.                        Uint8 historyLen, Uint8 iterations)
  324. {
  325.   int returnStatus = 0;
  326.   Sint16* input = NULL;
  327.   Uint8* outputs[2] = {NULL,NULL};
  328.   float quantErrors[2] = {2,2};
  329.   _IF_SDLERR(fileName==NULL,;,"!fileName")
  330.   _IF_SDLERR(pcm==NULL,;,"!pcm");
  331.   _IF_SDLERR(historyLen>63,;,"historyLen>63")
  332.   _IF_SDLERR(iterations==0,;,"iterations=0")
  333.  
  334.  
  335.   //fill in header info
  336.   _IF_SDLERR(pcm->numSamples==0,;,"numSamples==0")
  337.   kit_kmixerFDMHeader header;
  338.   header.magic.n    = FDM_MAGIC_NUMBER; //"fdm\x00"
  339.   header.headerSize = sizeof(kit_kmixerFDMHeader);
  340.   header.type       = 0; //type 0 = static delta, static history
  341.   float dataSizeMono = ceilf((float)pcm->numSamples/8);
  342.   header.dataSize   = dataSizeMono*(1+pcm->spec.stereo);
  343.   header.numSamples = pcm->numSamples;
  344.   header.loopStart  = pcm->loopStart;
  345.   header.loopEnd    = pcm->loopEnd;
  346.   header.delta      = 2.0f; //(final value calculated)
  347.   header.loopCount  = pcm->loopCount;
  348.   header.remainder  = pcm->numSamples%8;
  349.   header.historyLen = historyLen;
  350.   header.channels   = 1+pcm->spec.stereo;
  351.  
  352.  
  353.   //malloc i/o buffers
  354.   Uint64 numSamplesRaw = header.numSamples*header.channels;
  355.   input = SDL_malloc(sizeof(Sint16)*numSamplesRaw);
  356.   outputs[0] = SDL_malloc(header.dataSize);
  357.   outputs[1] = SDL_malloc(header.dataSize);
  358.   _IF_SDLERR(input==NULL,;,"!input")
  359.   _IF_SDLERR(outputs[0]==NULL,;,"!outputs[0]")
  360.   _IF_SDLERR(outputs[1]==NULL,;,"!outputs[1]")
  361.  
  362.  
  363.   //convert sample data to Sint16
  364.   Sint8* pcmI_8 = pcm->data;  Sint32* pcmI32 = pcm->i32;  float* pcmF32 = pcm->f32;
  365.   switch(pcm->spec.format){
  366.   case AUDIO_U8 : for(Uint64 i=0; i<numSamplesRaw; ++i){ input[i] =(pcmI_8[i]-128)<<8; } break;
  367.   case AUDIO_S16: kit_coreMemcpy(input,pcm->i16,sizeof(Sint16)*numSamplesRaw); break;
  368.   case AUDIO_S32: for(Uint64 i=0; i<numSamplesRaw; ++i){ input[i] = pcmI32[i]>>16; } break;
  369.   case AUDIO_F32: for(Uint64 i=0; i<numSamplesRaw; ++i){ input[i] = pcmF32[i]*32767; } }
  370.   for(Uint64 i=0; i<numSamplesRaw; ++i){ if(input[i]==-32768) input[i] = -32767; }
  371.  
  372.  
  373.   //encode i16 pcm stream
  374.   //int which = 0; //which output buffer to write to
  375.  
  376.  
  377.   //write output data to file
  378.    //write header info first
  379.   FILE* file = fopen(fileName,"wb");
  380.   _IF_SDLERR(file==NULL,;,"!file")
  381.   size_t headerBytes = fwrite(&header,1,sizeof(kit_kmixerFDMHeader),file);
  382.   _IF_SDLERR(headerBytes<sizeof(header),fclose(file),"headerBytes<sizeof(header)")
  383.  
  384.    //then write data
  385.   const size_t chunkSize = DEFAULT_RW_CHUNK_SIZE;
  386.   size_t bytesWritten, totalWritten = 0; //specifically data bytes
  387.   void* dataStart = (quantErrors[0]<quantErrors[1]) ? outputs[0] : outputs[1];
  388.   void* dataPtr   = dataStart;
  389.   void* dataEnd   = dataStart + header.dataSize;
  390.   while((dataPtr+chunkSize) < dataEnd){
  391.     bytesWritten = fwrite(dataPtr,1,chunkSize,file);
  392.     _IF_SDLERR(bytesWritten<chunkSize,fclose(file),"!fwrite; bytesWritten<chunkSize")
  393.     totalWritten += bytesWritten;
  394.     dataPtr += chunkSize;
  395.   }
  396.  
  397.   size_t remainingBytes = header.dataSize-totalWritten;
  398.   if(remainingBytes) bytesWritten = fwrite(dataPtr,1,remainingBytes,file);
  399.   _IF_SDLERR(bytesWritten<remainingBytes,fclose(file),"!fwrite; bytesWritten<remainingBytes")
  400.   int result = fclose(file);
  401.   _IF_SDLERR(result,;,"fclose()=%i",result)
  402.  
  403.  
  404.   ++returnStatus;
  405.   _error_:
  406.   --returnStatus;
  407.   if(input != NULL) SDL_free(input);
  408.   if(outputs[0] != NULL) SDL_free(outputs[0]);
  409.   if(outputs[1] != NULL) SDL_free(outputs[1]);
  410.   return returnStatus;
  411. }
Advertisement
Comments
  • optimaequipments
    1 year (edited)
    1. Title: "OptimaEquipments.com: Your Destination for High-Quality MRI and CT Scanners - Advancing Medical Imaging Technology"
    2. Introduction
    3. In the ever-evolving world of healthcare, medical imaging technology plays a pivotal role in diagnosing and treating various medical conditions. Among the most crucial equipment used in modern medical facilities are MRI (Magnetic Resonance Imaging) and CT (Computed Tomography) scanners. These state-of-the-art machines have revolutionized medical diagnostics and significantly improved patient care. If you're in the medical field and seeking high-quality medical imaging equipment,
    4. https://optimaequipments.com/
    5.  
    6.  
    7. [url=https://optimaequipments.com/product/mindray-m7-ultrasound-machine/]Mindray M7 Ultrasound Machine[/url]
    8. [url=https://optimaequipments.com/product/sonimage-hs1/]SONIMAGE® HS1[/url]
    9. [url=https://optimaequipments.com/product/sonimage-mx1/]SONIMAGE® MX1[/url]
    10. [url=https://optimaequipments.com/product/siemens-acuson-nx2-ultrasound-machine/]Siemens Acuson NX2 Ultrasound Machine[/url]
    11. [url=https://optimaequipments.com/product/siemens-acuson-nx3-elite-ultrasound-machine/]Siemens Acuson NX3 Elite Ultrasound Machine[/url]
    12. [url=https://optimaequipments.com/product/siemens-acuson-nx3-ultrasound-machine/]Siemens Acuson NX3 Ultrasound Machine[/url]
    13. [url=https://optimaequipments.com/product/siemens-acuson-p300-ultrasound-machine/]Siemens Acuson P300 Ultrasound Machine[/url]
    14. [url=https://optimaequipments.com/product/siemens-acuson-p500-ultrasound-machine/]Siemens Acuson P500 Ultrasound Machine[/url]
    15. [url=https://optimaequipments.com/product/siemens-acuson-s1000-ultrasound-machine/]Siemens Acuson S1000 Ultrasound Machine[/url]
    16. [url=https://optimaequipments.com/product/siemens-acuson-sc2000-ultrasound-machine/]Siemens Acuson SC2000 Ultrasound Machine[/url]
    17. [url=https://optimaequipments.com/product/siemens-acuson-x150-ultrasound-machine/]Siemens Acuson X150 Ultrasound Machine[/url]
    18. [url=https://optimaequipments.com/product/samsung-sonoace-r7-ultrasound-machine/]Samsung SonoAce R7 Ultrasound Machine[/url]
    19. [url=https://optimaequipments.com/product/samsung-accuvix-v10-ultrasound-machine/]Samsung Accuvix V10 Ultrasound Machine[/url]
    20. [url=https://optimaequipments.com/product/samsung-accuvix-xg-ultrasound-machine/]Samsung Accuvix XG Ultrasound Machine[/url]
    21. [url=https://optimaequipments.com/product/samsung-mysono-u6-ultrasound-machine/]Samsung MySono U6 Ultrasound Machine[/url]
    22. [url=https://optimaequipments.com/product/samsung-sonoace-r3-ultrasound-machine/]Samsung SonoAce R3 Ultrasound Machine[/url]
    23. [url=https://optimaequipments.com/product/samsung-sonoace-r5-ultrasound-machine/]Samsung SonoAce R5 Ultrasound Machine[/url]
    24. [url=https://optimaequipments.com/product/canon-aplio-300-cv-platinum-ultrasound-machine/]Canon Aplio 300 CV Platinum Ultrasound Machine[/url]
    25. [url=https://optimaequipments.com/product/canon-aplio-400-platinum-ultrasound-machine/]Canon Aplio 400 Platinum Ultrasound Machine[/url]
    26. [url=https://optimaequipments.com/product/canon-aplio-i700-ultrasound-machine/]Canon Aplio i700 Ultrasound Machine[/url]
    27. [url=https://optimaequipments.com/product/canon-artida-ultrasound-machine/]Canon Artida Ultrasound Machine[/url]
    28.  
    29.  
    30.  
Add Comment
Please, Sign In to add comment
Advertisement