# EMK File Support

Complete guide for playing EMK (Extreme Karaoke) files with automatic conversion.

## Overview

The `@karaplay/kar-player` now supports EMK files with automatic conversion to KAR format. EMK files are automatically detected and converted before playback.

**Powered by:** `@karaplay/file-coder@^1.3.1`

## Features

- ✅ **Auto-detect EMK files** - Automatic detection by extension or MIME type
- ✅ **Client-side conversion** - Browser-based EMK→KAR conversion
- ✅ **Server-side conversion** - Node.js EMK→KAR conversion
- ✅ **Batch processing** - Convert multiple EMK files at once (server)
- ✅ **Metadata extraction** - Get title, artist, code from EMK
- ✅ **Thai support** - Full Thai text support (TIS-620 encoding)
- ✅ **Next.js ready** - Works in both App Router and Pages Router

---

## Client-Side (Browser)

### 1. Auto-Convert and Play

```tsx
import { MIDIPlayer } from '@karaplay/kar-player';

const player = new MIDIPlayer();

const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
  const file = e.target.files?.[0];
  if (!file) return;

  // Auto-converts EMK to KAR if needed!
  await player.openFileWithEmkSupport(file);
  await player.play();
};

return <input type="file" accept=".emk,.kar" onChange={handleFileUpload} />;
```

### 2. Manual Conversion

```tsx
import { convertEmkToKarBrowser } from '@karaplay/kar-player';

const handleEmkFile = async (file: File) => {
  const result = await convertEmkToKarBrowser(file, {
    outputFilename: 'converted.kar'
  });

  if (result.success) {
    console.log('Title:', result.metadata.title);
    console.log('Artist:', result.metadata.artist);
    console.log('Code:', result.metadata.code);
    
    // Use with player
    player.openFile(new Uint8Array(result.karBuffer));
  } else {
    console.error('Conversion failed:', result.error);
  }
};
```

### 3. File Type Detection

```tsx
import { isEmkFile, isEmkFilename } from '@karaplay/kar-player';

const handleFile = async (file: File) => {
  if (isEmkFile(file)) {
    console.log('This is an EMK file!');
    // Handle EMK
  } else {
    console.log('This is a KAR/MIDI file');
    // Handle KAR/MIDI
  }
};

// Or check by filename
if (isEmkFilename('song.emk')) {
  console.log('Filename is EMK');
}
```

---

## Server-Side (Node.js)

### 1. Convert from File Path

```typescript
import { convertEmkToKarServer } from '@karaplay/kar-player';

const result = await convertEmkToKarServer('/path/to/song.emk', {
  outputPath: '/path/to/output.kar'
});

if (result.success) {
  console.log('Converted:', result.metadata.title);
  // result.karBuffer contains the KAR file as Buffer
}
```

### 2. Convert from Buffer

```typescript
import { convertEmkBufferToKar } from '@karaplay/kar-player';
import fs from 'fs';

// Read EMK file
const emkBuffer = fs.readFileSync('/path/to/song.emk');

// Convert
const result = await convertEmkBufferToKar(emkBuffer, 'song.emk');

if (result.success) {
  // Write KAR file
  fs.writeFileSync('/output/song.kar', result.karBuffer);
}
```

### 3. Batch Conversion

```typescript
import { convertEmkToKarBatchServer } from '@karaplay/kar-player';

const results = await convertEmkToKarBatchServer(
  [
    '/songs/song1.emk',
    '/songs/song2.emk',
    '/songs/song3.emk'
  ],
  '/output/directory'
);

results.forEach((result, index) => {
  if (result.success) {
    console.log(`✅ ${index + 1}. ${result.metadata.title}`);
  } else {
    console.error(`❌ ${index + 1}. ${result.error}`);
  }
});
```

---

## Next.js Integration

### App Router - API Route

```typescript
// app/api/convert-emk/route.ts
import { convertEmkBufferToKar } from '@karaplay/kar-player';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  try {
    // Get uploaded file
    const formData = await request.formData();
    const file = formData.get('file') as File;
    
    if (!file) {
      return NextResponse.json(
        { error: 'No file provided' }, 
        { status: 400 }
      );
    }

    // Convert EMK to KAR on server
    const buffer = Buffer.from(await file.arrayBuffer());
    const result = await convertEmkBufferToKar(buffer, file.name);

    if (!result.success) {
      return NextResponse.json(
        { error: result.error }, 
        { status: 500 }
      );
    }

    // Return KAR file as download
    return new NextResponse(result.karBuffer, {
      status: 200,
      headers: {
        'Content-Type': 'audio/midi',
        'Content-Disposition': `attachment; filename="${result.filename}"`,
      },
    });
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message }, 
      { status: 500 }
    );
  }
}
```

### App Router - Server Action

```typescript
// app/actions/convertEmk.ts
'use server';

import { convertEmkBufferToKar } from '@karaplay/kar-player';

export async function convertEmkAction(formData: FormData) {
  const file = formData.get('file') as File;
  
  if (!file) {
    return { success: false, error: 'No file provided' };
  }

  const buffer = Buffer.from(await file.arrayBuffer());
  const result = await convertEmkBufferToKar(buffer, file.name);

  if (!result.success) {
    return { success: false, error: result.error };
  }

  return {
    success: true,
    metadata: result.metadata,
    karData: Array.from(result.karBuffer) // Convert Buffer to array for serialization
  };
}
```

### Client Component with Server API

```tsx
'use client';

import { useState } from 'react';
import { MIDIPlayer, KarFile } from '@karaplay/kar-player';
import { useLyricRenderer } from '@karaplay/kar-player';

export default function EmkPlayer() {
  const [player] = useState(() => new MIDIPlayer());
  const [lyrics, setLyrics] = useState([]);
  const [currentTime, setCurrentTime] = useState(0);

  const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    try {
      // Option 1: Client-side conversion (fast for small files)
      await player.openFileWithEmkSupport(file);

      // Option 2: Server-side conversion (better for large files)
      /*
      const formData = new FormData();
      formData.append('file', file);
      
      const response = await fetch('/api/convert-emk', {
        method: 'POST',
        body: formData
      });
      
      const karBuffer = await response.arrayBuffer();
      player.openFile(new Uint8Array(karBuffer));
      */

      // Parse lyrics
      const arrayBuffer = await file.arrayBuffer();
      const karFile = new KarFile(arrayBuffer);
      setLyrics(karFile.lyrics);

      // Play
      await player.play();
      player.ontick = (song, time) => setCurrentTime(time);

    } catch (error) {
      console.error('Failed to load:', error);
    }
  };

  const { containerRef } = useLyricRenderer(lyrics, currentTime, {
    displayMode: 'extreme-karaoke',
    theme: 'extreme-karaoke'
  });

  return (
    <div>
      <input type="file" accept=".emk,.kar" onChange={handleFileUpload} />
      <div ref={containerRef} />
    </div>
  );
}
```

---

## API Reference

### Client-Side Functions

#### `convertEmkToKarBrowser(file, options?)`

Convert EMK file to KAR in browser.

**Parameters:**
- `file: File | Blob` - EMK file to convert
- `options?: { outputFilename?: string }` - Optional output filename

**Returns:** `Promise<EmkConversionResult>`

```typescript
interface EmkConversionResult {
  karBuffer: Buffer;
  metadata: {
    title?: string;
    artist?: string;
    code?: string;
  };
  filename: string;
  success: boolean;
  error?: string;
}
```

#### `isEmkFile(file)`

Check if a File or Blob is an EMK file.

**Parameters:**
- `file: File | Blob`

**Returns:** `boolean`

#### `isEmkFilename(filename)`

Check if a filename is an EMK file.

**Parameters:**
- `filename: string`

**Returns:** `boolean`

### Server-Side Functions

#### `convertEmkToKarServer(emkPath, options?)`

Convert EMK file to KAR on server.

**Parameters:**
- `emkPath: string` - Path to EMK file
- `options?: { outputPath?: string }` - Optional output path

**Returns:** `Promise<EmkConversionResultServer>`

```typescript
interface EmkConversionResultServer {
  karBuffer: Buffer;
  metadata: {
    title?: string;
    artist?: string;
    code?: string;
  };
  filename: string;
  success: boolean;
  error?: string;
}
```

#### `convertEmkBufferToKar(emkBuffer, filename)`

Convert EMK buffer to KAR on server.

**Parameters:**
- `emkBuffer: Buffer` - EMK file buffer
- `filename: string` - Original filename

**Returns:** `Promise<EmkConversionResultServer>`

#### `convertEmkToKarBatchServer(emkPaths, outputDir?)`

Batch convert multiple EMK files.

**Parameters:**
- `emkPaths: string[]` - Array of EMK file paths
- `outputDir?: string` - Optional output directory

**Returns:** `Promise<EmkConversionResultServer[]>`

---

## Examples

### Example 1: Complete EMK Player (Client-Side)

```tsx
'use client';

import { useState, useEffect } from 'react';
import { 
  MIDIPlayer, 
  SpessaSynthWrapper, 
  KarFile,
  useLyricRenderer 
} from '@karaplay/kar-player';

export default function EmkKaraokePlayer() {
  const [player] = useState(() => new MIDIPlayer());
  const [synth, setSynth] = useState<SpessaSynthWrapper | null>(null);
  const [lyrics, setLyrics] = useState([]);
  const [currentTime, setCurrentTime] = useState(0);
  const [isReady, setIsReady] = useState(false);

  // Initialize synthesizer
  useEffect(() => {
    const init = async () => {
      const audioContext = new AudioContext();
      const synthInstance = new SpessaSynthWrapper(audioContext);
      
      await synthInstance.initialize('/spessasynth_processor.min.js');
      await synthInstance.loadSoundFont('/soundfont.sf2');
      
      player.setSoundfontEngine('spessasynth', synthInstance);
      setSynth(synthInstance);
      setIsReady(true);
    };
    
    init();
  }, []);

  const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file || !isReady) return;

    try {
      // Auto-convert EMK or load KAR
      await player.openFileWithEmkSupport(file);

      // Parse lyrics
      const arrayBuffer = await file.arrayBuffer();
      const karFile = new KarFile(arrayBuffer);
      setLyrics(karFile.lyrics);

      // Play
      await player.play();

      // Update time
      player.ontick = (song, time) => {
        setCurrentTime(time);
      };

      console.log('✅ Playing:', file.name);
    } catch (error) {
      console.error('❌ Failed to play:', error);
    }
  };

  const { containerRef } = useLyricRenderer(lyrics, currentTime, {
    displayMode: 'extreme-karaoke',
    highlightMode: 'word',
    theme: 'extreme-karaoke'
  });

  return (
    <div>
      <h1>EMK/KAR Player</h1>
      <input 
        type="file" 
        accept=".emk,.kar,.mid" 
        onChange={handleFileUpload}
        disabled={!isReady}
      />
      {!isReady && <p>Loading synthesizer...</p>}
      <div ref={containerRef} style={{ marginTop: '2rem' }} />
    </div>
  );
}
```

### Example 2: Next.js API Route for EMK Conversion

```typescript
// app/api/convert-emk/route.ts
import { convertEmkBufferToKar } from '@karaplay/kar-player';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  try {
    const formData = await request.formData();
    const file = formData.get('file') as File;
    
    if (!file) {
      return NextResponse.json(
        { error: 'No file provided' }, 
        { status: 400 }
      );
    }

    console.log('Converting EMK file:', file.name);

    // Convert on server
    const buffer = Buffer.from(await file.arrayBuffer());
    const result = await convertEmkBufferToKar(buffer, file.name);

    if (!result.success) {
      return NextResponse.json(
        { error: result.error }, 
        { status: 500 }
      );
    }

    console.log('✅ Converted:', result.metadata);

    // Return as JSON with metadata
    return NextResponse.json({
      success: true,
      metadata: result.metadata,
      filename: result.filename,
      size: result.karBuffer.length
    });

    // Or return KAR file directly for download:
    /*
    return new NextResponse(result.karBuffer, {
      headers: {
        'Content-Type': 'audio/midi',
        'Content-Disposition': `attachment; filename="${result.filename}"`,
      },
    });
    */
  } catch (error: any) {
    console.error('❌ Conversion error:', error);
    return NextResponse.json(
      { error: error.message }, 
      { status: 500 }
    );
  }
}
```

### Example 3: Batch EMK Conversion (Server)

```typescript
// scripts/convert-emk-batch.ts
import { convertEmkToKarBatchServer } from '@karaplay/kar-player';
import path from 'path';
import fs from 'fs';

async function convertAllEmkFiles() {
  const emkDir = './songs/emk';
  const outputDir = './songs/kar';

  // Ensure output directory exists
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  // Get all EMK files
  const emkFiles = fs.readdirSync(emkDir)
    .filter(file => file.toLowerCase().endsWith('.emk'))
    .map(file => path.join(emkDir, file));

  console.log(`Found ${emkFiles.length} EMK files`);

  // Convert batch
  const results = await convertEmkToKarBatchServer(emkFiles, outputDir);

  // Report results
  const successful = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);

  console.log(`\n✅ Successful: ${successful.length}`);
  successful.forEach(r => {
    console.log(`  - ${r.metadata.title} (${r.filename})`);
  });

  if (failed.length > 0) {
    console.log(`\n❌ Failed: ${failed.length}`);
    failed.forEach(r => {
      console.log(`  - ${r.filename}: ${r.error}`);
    });
  }
}

convertAllEmkFiles();
```

---

## Supported File Types

| Format | Extension | Client-Side | Server-Side | Auto-Convert |
|--------|-----------|-------------|-------------|--------------|
| **KAR** | `.kar` | ✅ Native | ✅ Native | N/A |
| **MIDI** | `.mid` | ✅ Native | ✅ Native | N/A |
| **EMK** | `.emk` | ✅ Convert | ✅ Convert | ✅ Auto |

---

## Metadata Extraction

EMK files contain embedded metadata:

```typescript
const result = await convertEmkToKarBrowser(emkFile);

console.log('Title:', result.metadata.title);
console.log('Artist:', result.metadata.artist);
console.log('Code:', result.metadata.code);
```

**Example output:**
```json
{
  "title": "รักนี้หัวใจออกแบบมาเพื่อเธอ",
  "artist": "กะลา",
  "code": "Z2510001"
}
```

---

## Performance

### Client-Side
- Small EMK files (<1MB): ~100-300ms
- Medium EMK files (1-5MB): ~500ms-1s
- Large EMK files (>5MB): Consider server-side

### Server-Side
- Faster for large files
- No browser memory limits
- Better for batch processing

---

## Error Handling

```typescript
const result = await convertEmkToKarBrowser(file);

if (!result.success) {
  // Handle error
  console.error('Conversion failed:', result.error);
  
  // Show user-friendly message
  alert(`Failed to convert ${file.name}: ${result.error}`);
} else {
  // Success
  console.log('Converted successfully:', result.metadata);
}
```

---

## Thai Language Support

EMK files with Thai lyrics are fully supported:

**Encoding:** TIS-620 (Thai Industrial Standard)

**Example:**
```typescript
const result = await convertEmkToKarBrowser(thaiEmkFile);

// Thai text is readable!
console.log('Title:', result.metadata.title);
// → "รักนี้หัวใจออกแบบมาเพื่อเธอ"

console.log('Artist:', result.metadata.artist);
// → "กะลา"
```

---

## Troubleshooting

### Issue: "Cannot find module '@karaplay/file-coder'"

**Solution:**
```bash
npm install @karaplay/file-coder
```

The dependency should auto-install with `@karaplay/kar-player@0.12.0+`

### Issue: "Conversion failed"

**Possible causes:**
1. Invalid EMK file format
2. Corrupted file
3. Unsupported EMK version

**Debug:**
```typescript
const result = await convertEmkToKarBrowser(file);
if (!result.success) {
  console.error('Error:', result.error);
}
```

### Issue: Thai text garbled

**Solution:**
EMK files use TIS-620 encoding. The converter handles this automatically. If you still see garbled text, the EMK file may be corrupted.

---

## Best Practices

### 1. Client-Side for Small Files
```typescript
// For user uploads (<5MB)
await player.openFileWithEmkSupport(file);
```

### 2. Server-Side for Large Files
```typescript
// For large EMK files or batch processing
const result = await convertEmkToKarServer(path);
```

### 3. Cache Converted Files
```typescript
// On server
const cacheKey = `kar_${emkFilename}`;
if (cache.has(cacheKey)) {
  return cache.get(cacheKey);
}

const result = await convertEmkToKarServer(emkPath);
cache.set(cacheKey, result.karBuffer);
```

### 4. Error Boundaries
```tsx
<ErrorBoundary fallback={<div>Failed to load file</div>}>
  <EmkPlayer />
</ErrorBoundary>
```

---

## Migration Guide

### From Manual Conversion

**Before:**
```typescript
// Manual steps
const emk = await decodeEmk(file);
const kar = await convertToKar(emk);
player.openFile(kar);
```

**After:**
```typescript
// One line!
await player.openFileWithEmkSupport(file);
```

### From Separate Libraries

**Before:**
```typescript
import { decodeEmk } from 'emk-decoder';
import { convertToKar } from 'kar-converter';
```

**After:**
```typescript
import { convertEmkToKarBrowser } from '@karaplay/kar-player';
```

---

## Testing

The library includes comprehensive EMK tests:

```bash
npm test
```

**Test coverage:**
- ✅ EMK file detection
- ✅ Filename validation
- ✅ Extension extraction
- ✅ Browser conversion (integration)
- ✅ Server conversion (integration)

---

## License

MIT

## Support

For issues with EMK conversion, see `@karaplay/file-coder` documentation:
- [GitHub](https://github.com/karaplay/file-coder)
- [npm](https://www.npmjs.com/package/@karaplay/file-coder)
