Subversion Repositories svn.mios32

Rev

Rev 2057 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1203 tk 1
// $Id: seq_file_bm.c 2634 2019-01-05 16:51:12Z tk $
2
/*
3
 * Bookmark File access functions
4
 *
5
 * NOTE: before accessing the SD Card, the upper level function should
6
 * synchronize with the SD Card semaphore!
7
 *   MUTEX_SDCARD_TAKE; // to take the semaphore
8
 *   MUTEX_SDCARD_GIVE; // to release the semaphore
9
 *
10
 * ==========================================================================
11
 *
12
 *  Copyright (C) 2008 Thorsten Klose (tk@midibox.org)
13
 *  Licensed for personal non-commercial use only.
14
 *  All other rights reserved.
15
 *
16
 * ==========================================================================
17
 */
18
 
19
/////////////////////////////////////////////////////////////////////////////
20
// Include files
21
/////////////////////////////////////////////////////////////////////////////
22
 
23
#include <mios32.h>
24
 
25
#include <string.h>
26
 
1261 tk 27
#include "file.h"
1203 tk 28
#include "seq_file.h"
29
#include "seq_file_bm.h"
30
 
31
#include "seq_ui.h"
32
 
33
 
34
/////////////////////////////////////////////////////////////////////////////
35
// for optional debugging messages via DEBUG_MSG (defined in mios32_config.h)
36
/////////////////////////////////////////////////////////////////////////////
37
 
38
// Note: verbose level 1 is default - it prints error messages!
39
#define DEBUG_VERBOSE_LEVEL 1
40
 
41
 
42
/////////////////////////////////////////////////////////////////////////////
43
// Local definitions
44
/////////////////////////////////////////////////////////////////////////////
45
 
46
// in which subdirectory of the SD card are the MBSEQ files located?
47
// use "/" for root
48
// use "/<dir>/" for a subdirectory in root
49
// use "/<dir>/<subdir>/" to reach a subdirectory in <dir>, etc..
50
 
51
#define SEQ_FILES_PATH "/"
52
//#define SEQ_FILES_PATH "/MySongs/"
53
 
54
 
55
/////////////////////////////////////////////////////////////////////////////
56
// Local types
57
/////////////////////////////////////////////////////////////////////////////
58
 
59
// file informations stored in RAM
60
typedef struct {
61
  unsigned valid: 1;   // file is accessible
62
} seq_file_bm_info_t;
63
 
64
 
65
/////////////////////////////////////////////////////////////////////////////
66
// Local prototypes
67
/////////////////////////////////////////////////////////////////////////////
68
 
69
 
70
/////////////////////////////////////////////////////////////////////////////
71
// Local variables
72
/////////////////////////////////////////////////////////////////////////////
73
 
1205 tk 74
// 0: session
75
// 1: global
76
static seq_file_bm_info_t seq_file_bm_info[2];
1203 tk 77
 
78
 
79
/////////////////////////////////////////////////////////////////////////////
80
// Initialisation
81
/////////////////////////////////////////////////////////////////////////////
82
s32 SEQ_FILE_BM_Init(u32 mode)
83
{
84
  // invalidate file info
1205 tk 85
  SEQ_FILE_BM_Unload(0);
86
  SEQ_FILE_BM_Unload(1);
1203 tk 87
 
88
  return 0; // no error
89
}
90
 
91
 
92
/////////////////////////////////////////////////////////////////////////////
93
// Loads config file
94
// Called from SEQ_FILE_CheckSDCard() when the SD card has been connected
95
// returns < 0 on errors
96
/////////////////////////////////////////////////////////////////////////////
1205 tk 97
s32 SEQ_FILE_BM_Load(char *session, u8 global)
1203 tk 98
{
99
  s32 error;
1205 tk 100
  error = SEQ_FILE_BM_Read(session, global);
1203 tk 101
#if DEBUG_VERBOSE_LEVEL >= 2
102
  DEBUG_MSG("[SEQ_FILE_BM] Tried to open config file, status: %d\n", error);
103
#endif
104
 
105
  return error;
106
}
107
 
108
 
109
/////////////////////////////////////////////////////////////////////////////
110
// Unloads config file
111
// Called from SEQ_FILE_CheckSDCard() when the SD card has been disconnected
112
// returns < 0 on errors
113
/////////////////////////////////////////////////////////////////////////////
1205 tk 114
s32 SEQ_FILE_BM_Unload(u8 global)
1203 tk 115
{
1205 tk 116
  seq_file_bm_info[global].valid = 0;
1203 tk 117
 
118
  return 0; // no error
119
}
120
 
121
 
122
 
123
/////////////////////////////////////////////////////////////////////////////
124
// Returns 1 if config file valid
125
// Returns 0 if config file not valid
126
/////////////////////////////////////////////////////////////////////////////
1205 tk 127
s32 SEQ_FILE_BM_Valid(u8 global)
1203 tk 128
{
1205 tk 129
  return seq_file_bm_info[global].valid;
1203 tk 130
}
131
 
132
 
133
/////////////////////////////////////////////////////////////////////////////
134
// help function which parses a decimal or hex value
135
// returns >= 0 if value is valid
1986 tk 136
// returns -256 if value is invalid
1203 tk 137
/////////////////////////////////////////////////////////////////////////////
138
static s32 get_dec(char *word)
139
{
140
  if( word == NULL )
141
    return -1;
142
 
143
  char *next;
144
  long l = strtol(word, &next, 0);
145
 
146
  if( word == next )
147
    return -256; // modification for SEQ_FILE_BM: return -256 if value invalid, since we read signed bytes
148
 
149
  return l; // value is valid
150
}
151
 
152
 
153
/////////////////////////////////////////////////////////////////////////////
154
// reads the config file content (again)
155
// returns < 0 on errors (error codes are documented in seq_file.h)
156
/////////////////////////////////////////////////////////////////////////////
1205 tk 157
s32 SEQ_FILE_BM_Read(char *session, u8 global)
1203 tk 158
{
159
  s32 status = 0;
1205 tk 160
  seq_file_bm_info_t *info = &seq_file_bm_info[global];
1261 tk 161
  file_t file;
1203 tk 162
 
163
  info->valid = 0; // will be set to valid if file content has been read successfully
164
 
165
  char filepath[MAX_PATH];
1205 tk 166
  if( global )
167
    sprintf(filepath, "%sMBSEQ_BM.V4", SEQ_FILES_PATH);
168
  else
169
    sprintf(filepath, "%s/%s/MBSEQ_BM.V4", SEQ_FILE_SESSION_PATH, session);
1203 tk 170
 
171
#if DEBUG_VERBOSE_LEVEL >= 2
172
  DEBUG_MSG("[SEQ_FILE_BM] Open config file '%s'\n", filepath);
173
#endif
174
 
1261 tk 175
  if( (status=FILE_ReadOpen(&file, filepath)) < 0 ) {
1203 tk 176
#if DEBUG_VERBOSE_LEVEL >= 2
177
    DEBUG_MSG("[SEQ_FILE_BM] failed to open file, status: %d\n", status);
178
#endif
179
    return status;
180
  }
181
 
182
  // read config values
183
  u8 current_bookmark = 0;
184
  char line_buffer[128];
185
  do {
1261 tk 186
    status=FILE_ReadLine((u8 *)line_buffer, 128);
1203 tk 187
 
188
    if( status > 1 ) {
189
#if DEBUG_VERBOSE_LEVEL >= 3
190
      DEBUG_MSG("[SEQ_FILE_BM] read: %s", line_buffer);
191
#endif
192
 
193
      // sscanf consumes too much memory, therefore we parse directly
194
      char *separators = " \t";
195
      char *brkt;
196
      char *parameter;
197
 
198
      if( (parameter = strtok_r(line_buffer, separators, &brkt)) ) {
199
 
200
    u8 parameter_enabled = 1;
201
    if( *parameter == '-' ) {
202
      parameter_enabled = 0;
203
      ++parameter;
204
    } else if( *parameter == '+' ) {
205
      ++parameter;
206
    }
207
 
208
    if( *parameter == '#' ) {
209
      // ignore comments
210
    } else if( strcmp(parameter, "Slot") == 0 ) {
211
      char *word = strtok_r(NULL, separators, &brkt);
212
      s32 value = get_dec(word);
213
      if( value < 1 || value > SEQ_UI_BOOKMARKS_NUM ) {
214
#if DEBUG_VERBOSE_LEVEL >= 1
215
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid Slot number %d\n", value);
216
#endif
217
      } else {
218
        current_bookmark = value - 1;
219
      }
220
    } else if( strcmp(parameter, "Name") == 0 ) {
221
      //char *word = strtok_r(NULL, separators, &brkt);
222
      seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
223
      //strncpy(bm->name, word, 6);
224
      //strncpy(bm->name, (char *)&line_buffer+5, 6); // allow spaces...
225
      strncpy(bm->name, brkt, 6); // allow spaces...
226
    } else if( strcmp(parameter, "ParLayer") == 0 ) {
227
      char *word = strtok_r(NULL, separators, &brkt);
228
      int layer = word[0] - 'A';
229
      if( layer < 0 || layer > 16 ) {
230
#if DEBUG_VERBOSE_LEVEL >= 1
231
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid ParLayer '%s' in Slot %d\n", word, current_bookmark+1);
232
#endif
233
      } else {
234
        seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
235
        bm->par_layer = layer;
236
        bm->enable.PAR_LAYER = parameter_enabled;
237
      }
238
    } else if( strcmp(parameter, "TrgLayer") == 0 ) {
239
      char *word = strtok_r(NULL, separators, &brkt);
240
      int layer = word[0] - 'A';
241
      if( layer < 0 || layer > 16 ) {
242
#if DEBUG_VERBOSE_LEVEL >= 1
243
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid TrgLayer '%s' in Slot %d\n", word, current_bookmark+1);
244
#endif
245
      } else {
246
        seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
247
        bm->trg_layer = layer;
248
        bm->enable.TRG_LAYER = parameter_enabled;
249
      }
250
    } else if( strcmp(parameter, "Tracks") == 0 ) {
251
      char *word = strtok_r(NULL, separators, &brkt);
252
      if( strlen(word) < 16 ) {
253
#if DEBUG_VERBOSE_LEVEL >= 1
254
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid Tracks Parameter '%s' in Slot %d\n", word, current_bookmark+1);
255
#endif
256
      } else {
257
        seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
258
        bm->tracks = 0;
259
        int i;
260
        for(i=0; i<16; ++i)
261
          if( word[i] == '1' )
262
        bm->tracks |= (1 << i);
263
        bm->enable.TRACKS = parameter_enabled;
264
      }
265
    } else if( strcmp(parameter, "Mutes") == 0 ) {
266
      char *word = strtok_r(NULL, separators, &brkt);
267
      if( strlen(word) < 16 ) {
268
#if DEBUG_VERBOSE_LEVEL >= 1
269
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid Mutes Parameter '%s' in Slot %d\n", word, current_bookmark+1);
270
#endif
271
      } else {
272
        seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
273
        bm->mutes = 0;
274
        int i;
275
        for(i=0; i<16; ++i)
276
          if( word[i] == '1' )
277
        bm->mutes |= (1 << i);
278
        bm->enable.MUTES = parameter_enabled;
279
      }
2039 tk 280
    } else if( strcmp(parameter, "Page") == 0 ) {
2051 tk 281
#ifndef MBSEQV4L
2039 tk 282
      char *word = strtok_r(NULL, separators, &brkt);
283
      seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
284
 
285
      seq_ui_page_t page = SEQ_UI_PAGES_CfgNameSearch(word);
286
      if( page == SEQ_UI_PAGE_NONE ) {
287
        s32 value = get_dec(word);
288
        if( value <= 0 ) {
289
#if DEBUG_VERBOSE_LEVEL >= 1
2634 tk 290
          DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid value '%s' for parameter '%s' in Slot %d\n", word, parameter, current_bookmark+1);
2039 tk 291
#endif
292
        } else {
293
          page = SEQ_UI_PAGES_OldBmIndexSearch(value);
294
        }
295
      }
296
 
297
      if( page == SEQ_UI_PAGE_NONE ) {
298
#if DEBUG_VERBOSE_LEVEL >= 1
2634 tk 299
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid value '%s' for parameter '%s' in Slot %d\n", word, parameter, current_bookmark+1);
2039 tk 300
#endif
301
      } else {
302
        bm->page = page;
303
        bm->enable.PAGE = parameter_enabled;
304
      }
2051 tk 305
#endif
1203 tk 306
    } else {
307
      char *word = strtok_r(NULL, separators, &brkt);
308
      s32 value = get_dec(word);
309
      seq_ui_bookmark_t *bm = &seq_ui_bookmarks[current_bookmark];
310
 
311
      if( value < 0 ) {
312
#if DEBUG_VERBOSE_LEVEL >= 1
2634 tk 313
        DEBUG_MSG("[SEQ_FILE_BM] ERROR invalid value '%s', for parameter '%s' in Slot %d\n", word, parameter, current_bookmark+1);
1203 tk 314
#endif
315
      } else if( strcmp(parameter, "Group") == 0 ) {
316
        bm->group = value - 1;
317
        bm->enable.GROUP = parameter_enabled;
318
      } else if( strcmp(parameter, "Instrument") == 0 ) {
319
        bm->instrument = value - 1;
320
        bm->enable.INSTRUMENT = parameter_enabled;
321
      } else if( strcmp(parameter, "StepView") == 0 ) {
322
        bm->step_view = value - 1;
323
        bm->enable.STEP_VIEW = parameter_enabled;
324
      } else if( strcmp(parameter, "Step") == 0 ) {
325
        bm->step = value - 1;
326
        bm->enable.STEP = parameter_enabled;
327
      } else if( strcmp(parameter, "EditView") == 0 ) {
1204 tk 328
        bm->edit_view = value;
1203 tk 329
        bm->enable.EDIT_VIEW = parameter_enabled;
330
      } else if( strcmp(parameter, "Solo") == 0 ) {
331
        bm->flags.SOLO = value ? 1 : 0;
332
        bm->enable.SOLO = parameter_enabled;
333
      } else if( strcmp(parameter, "All") == 0 ) {
334
        bm->flags.CHANGE_ALL_STEPS = value ? 1 : 0;
335
        bm->enable.CHANGE_ALL_STEPS = parameter_enabled;
336
      } else if( strcmp(parameter, "Fast") == 0 ) {
337
        bm->flags.FAST = value ? 1 : 0;
338
        bm->enable.FAST = parameter_enabled;
339
      } else if( strcmp(parameter, "Metronome") == 0 ) {
340
        bm->flags.METRONOME = value ? 1 : 0;
341
        bm->enable.METRONOME = parameter_enabled;
342
      } else if( strcmp(parameter, "LoopMode") == 0 ) {
343
        bm->flags.LOOP = value ? 1 : 0;
344
        bm->enable.LOOP = parameter_enabled;
345
      } else if( strcmp(parameter, "FollowMode") == 0 ) {
346
        bm->flags.FOLLOW = value ? 1 : 0;
347
        bm->enable.FOLLOW = parameter_enabled;
348
      } else {
349
#if DEBUG_VERBOSE_LEVEL >= 1
2634 tk 350
        DEBUG_MSG("[SEQ_FILE_HW] ERROR: unknown parameter: %s in Slot %d", line_buffer, current_bookmark+1);
1203 tk 351
#endif
352
      }
353
    }
354
      } else {
355
#if DEBUG_VERBOSE_LEVEL >= 1
356
    DEBUG_MSG("[SEQ_FILE_BM] ERROR no space separator in following line: %s", line_buffer);
357
#endif
358
      }
359
    }
360
 
361
  } while( status >= 1 );
362
 
363
  // close file
1261 tk 364
  status |= FILE_ReadClose(&file);
1203 tk 365
 
366
  if( status < 0 ) {
367
#if DEBUG_VERBOSE_LEVEL >= 1
368
    DEBUG_MSG("[SEQ_FILE_BM] ERROR while reading file, status: %d\n", status);
369
#endif
370
    return SEQ_FILE_BM_ERR_READ;
371
  }
372
 
373
  // file is valid! :)
374
  info->valid = 1;
375
 
376
  return 0; // no error
377
}
378
 
379
 
380
/////////////////////////////////////////////////////////////////////////////
381
// help function to write data into file or send to debug terminal
382
// returns < 0 on errors (error codes are documented in seq_file.h)
383
/////////////////////////////////////////////////////////////////////////////
1205 tk 384
static s32 SEQ_FILE_BM_Write_Hlp(u8 write_to_file, u8 global)
1203 tk 385
{
386
  s32 status = 0;
387
  char line_buffer[200];
388
 
1261 tk 389
#define FLUSH_BUFFER if( !write_to_file ) { DEBUG_MSG(line_buffer); } else { status |= FILE_WriteBuffer((u8 *)line_buffer, strlen(line_buffer)); }
1203 tk 390
 
1205 tk 391
  u8 from_bookmark = global ? 0 : 8;
392
  u8 to_bookmark = global ? 7 : (SEQ_UI_BOOKMARKS_NUM-1);
393
 
1203 tk 394
  // write bookmarks
395
  u8 bookmark;
1205 tk 396
  seq_ui_bookmark_t *bm = &seq_ui_bookmarks[from_bookmark];
397
  for(bookmark=from_bookmark; bookmark<=to_bookmark; ++bookmark, ++bm) {
1203 tk 398
    int i;
399
 
400
    sprintf(line_buffer, "####################\n");
401
    FLUSH_BUFFER;
402
    sprintf(line_buffer, "Slot %d\n", bookmark+1);
403
    FLUSH_BUFFER;
404
    sprintf(line_buffer, "Name %s\n", bm->name);
405
    FLUSH_BUFFER;
406
    sprintf(line_buffer, "####################\n");
407
    FLUSH_BUFFER;
408
 
2039 tk 409
#ifdef MBSEQV4L
410
    sprintf(line_buffer, "%cPage %s\n", bm->enable.PAGE ? '+' : '-', bm->page);
411
#else
412
    sprintf(line_buffer, "%cPage %s\n", bm->enable.PAGE ? '+' : '-', SEQ_UI_PAGES_CfgNameGet(bm->page));
413
#endif
1203 tk 414
    FLUSH_BUFFER;
415
    sprintf(line_buffer, "%cGroup %d\n", bm->enable.GROUP ? '+' : '-', bm->group+1);
416
    FLUSH_BUFFER;
417
 
418
    sprintf(line_buffer, "%cTracks xxxxxxxxxxxxxxxx\n", bm->enable.TRACKS ? '+' : '-');
419
    for(i=0; i<16; ++i)
420
      line_buffer[8+i] = (bm->tracks & (1 << i)) ? '1' : '0';
421
    FLUSH_BUFFER;
422
 
423
    sprintf(line_buffer, "%cMutes xxxxxxxxxxxxxxxx\n", bm->enable.MUTES ? '+' : '-');
424
    for(i=0; i<16; ++i)
425
      line_buffer[7+i] = (bm->mutes & (1 << i)) ? '1' : '0';
426
    FLUSH_BUFFER;
427
 
428
    sprintf(line_buffer, "%cParLayer %c\n", bm->enable.PAR_LAYER ? '+' : '-', 'A'+bm->par_layer);
429
    FLUSH_BUFFER;
430
    sprintf(line_buffer, "%cTrgLayer %c\n", bm->enable.TRG_LAYER ? '+' : '-', 'A'+bm->trg_layer);
431
    FLUSH_BUFFER;
432
    sprintf(line_buffer, "%cInstrument %d\n", bm->enable.INSTRUMENT ? '+' : '-', bm->instrument+1);
433
    FLUSH_BUFFER;
434
    sprintf(line_buffer, "%cStepView %d\n", bm->enable.STEP_VIEW ? '+' : '-', (int)bm->step_view+1);
435
    FLUSH_BUFFER;
436
    sprintf(line_buffer, "%cStep %d\n", bm->enable.STEP ? '+' : '-', (int)bm->step+1);
437
    FLUSH_BUFFER;
1204 tk 438
    sprintf(line_buffer, "%cEditView %d\n", bm->enable.EDIT_VIEW ? '+' : '-', bm->edit_view);
1203 tk 439
    FLUSH_BUFFER;
440
    sprintf(line_buffer, "%cSolo %d\n", bm->enable.SOLO ? '+' : '-', bm->flags.SOLO);
441
    FLUSH_BUFFER;
442
    sprintf(line_buffer, "%cAll %d\n", bm->enable.CHANGE_ALL_STEPS ? '+' : '-', bm->flags.CHANGE_ALL_STEPS);
443
    FLUSH_BUFFER;
444
    sprintf(line_buffer, "%cFast %d\n", bm->enable.FAST ? '+' : '-', bm->flags.FAST);
445
    FLUSH_BUFFER;
446
    sprintf(line_buffer, "%cMetronome %d\n", bm->enable.METRONOME ? '+' : '-', bm->flags.METRONOME);
447
    FLUSH_BUFFER;
448
    sprintf(line_buffer, "%cLoopMode %d\n", bm->enable.LOOP ? '+' : '-', bm->flags.LOOP);
449
    FLUSH_BUFFER;
450
    sprintf(line_buffer, "%cFollowMode %d\n", bm->enable.FOLLOW ? '+' : '-', bm->flags.FOLLOW);
451
    FLUSH_BUFFER;
452
 
453
 
454
    sprintf(line_buffer, "\n");
455
    FLUSH_BUFFER;
456
  }
457
 
458
  return status;
459
}
460
 
461
/////////////////////////////////////////////////////////////////////////////
462
// writes the groove file
463
// returns < 0 on errors (error codes are documented in seq_file.h)
464
/////////////////////////////////////////////////////////////////////////////
1205 tk 465
s32 SEQ_FILE_BM_Write(char* session, u8 global)
1203 tk 466
{
1205 tk 467
  seq_file_bm_info_t *info = &seq_file_bm_info[global];
1203 tk 468
 
469
  char filepath[MAX_PATH];
1205 tk 470
  if( global )
471
    sprintf(filepath, "%sMBSEQ_BM.V4", SEQ_FILES_PATH);
472
  else
473
    sprintf(filepath, "%s/%s/MBSEQ_BM.V4", SEQ_FILE_SESSION_PATH, session);
1203 tk 474
 
475
#if DEBUG_VERBOSE_LEVEL >= 2
476
  DEBUG_MSG("[SEQ_FILE_BM] Open config file '%s' for writing\n", filepath);
477
#endif
478
 
479
  s32 status = 0;
1261 tk 480
  if( (status=FILE_WriteOpen(filepath, 1)) < 0 ) {
1203 tk 481
#if DEBUG_VERBOSE_LEVEL >= 1
482
    DEBUG_MSG("[SEQ_FILE_BM] Failed to open/create config file, status: %d\n", status);
483
#endif
1261 tk 484
    FILE_WriteClose(); // important to free memory given by malloc
1203 tk 485
    info->valid = 0;
486
    return status;
487
  }
488
 
489
  // write file
1205 tk 490
  status |= SEQ_FILE_BM_Write_Hlp(1, global);
1203 tk 491
 
492
  // close file
1261 tk 493
  status |= FILE_WriteClose();
1203 tk 494
 
495
  // check if file is valid
496
  if( status >= 0 )
497
    info->valid = 1;
498
 
499
#if DEBUG_VERBOSE_LEVEL >= 2
500
  DEBUG_MSG("[SEQ_FILE_BM] config file written with status %d\n", status);
501
#endif
502
 
503
  return (status < 0) ? SEQ_FILE_BM_ERR_WRITE : 0;
504
}
505
 
506
/////////////////////////////////////////////////////////////////////////////
507
// sends groove data to debug terminal
508
// returns < 0 on errors
509
/////////////////////////////////////////////////////////////////////////////
1205 tk 510
s32 SEQ_FILE_BM_Debug(u8 global)
1203 tk 511
{
1205 tk 512
  return SEQ_FILE_BM_Write_Hlp(0, global); // send to debug terminal
1203 tk 513
}